I'm fighting issues with routing in an MVC 3 Web API. It seems like it should be pretty simple, but I'm not making any headway.
My error is:
<Error>
<Message>The request is invalid.</Message>
<MessageDetail>
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'Boolean NewViolationsPublished(Int32)' in 'BPA.API.Controllers.CacheManagementController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
</MessageDetail>
</Error>
My RegisterRoutes is such:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "NukeAllItemsFromCache",
routeTemplate: "api/CacheManagement/NukeAllItemsFromCache");
routes.MapHttpRoute(
name: "ControllerAndAction",
routeTemplate: "api/{controller}/{action}"
);
routes.MapHttpRoute(
name: "ControllerAndActionAndId",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional, action = "Get" },
constraints: new { id = #"^\d+$" } // Only integers
);
routes.MapHttpRoute(
name: "ControllerAndId",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = #"^\d+$" } // Only integers
);
}
And my controller is (i took out the code for ease of reading):
public class CacheManagementController : ApiController
{
public CacheManagementController()
[HttpGet]
public bool NewViolationsPublished(int id)
[HttpGet]
public bool IsCached(CachedItemLabels label, int clientId)
[HttpGet]
public void RemoveItemFromCache(int clientId, CachedItemLabels cacheLabel, string test)
[HttpGet]
public string NukeAllItemsFromCache()
}
I get the error when I try to call:
http://localhost/api/CacheManagement/NukeAllItemsFromCache
TIA
Are you sure you're defining your routes in the right place? Web API routes should be configured using System.Web.Http.GlobalConfiguration.Configuration.Routes.MapHttpRoute (normally in a WebApiConfig.cs file in App_Start), as shown on http://www.asp.net/web-api/overview/extensibility/configuring-aspnet-web-api, which is different from the route configuration used by vanilla MVC.
I think you may just be hitting the default Web API route, which is api/{controller}/{id}, with ID optional. The action is determined by the HTTP verb, which in this case is GET, and all of your methods match that by virtue of the [HttpGet].
Edit: example
Global.asax
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
GlobalModifiers.ApplyGlobalConfiguration(this, true);
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "action-specific",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
I beleave your problem is in this route:
routes.MapHttpRoute(
name: "ControllerAndActionAndId",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional, action = "Get" },
constraints: new { id = #"^\d+$" } // Only integers
);
You specify id as an optional parameter and at the same time constrain it to actually be a non-nullable number. If /api/CacheManagement/NukeAllItemsFromCache is an exception, then add additional route for it, otherwise ease a constraint to something like #"^\d*$" if possible.
You are missing the default action for the route you configured:
routes.MapHttpRoute(
name: "NukeAllItemsFromCache",
routeTemplate: "api/CacheManagement/NukeAllItemsFromCache");
should be
routes.MapHttpRoute(
name: "NukeAllItemsFromCache",
routeTemplate: "api/CacheManagement/NukeAllItemsFromCache",
defaults: new { action = "NukeAllItemsFromCache" }
);
You tried to put parameter id as nullable like:
[HttpGet]
public bool NewViolationsPublished(int? id)
or
[HttpGet]
public bool NewViolationsPublished(Nullable<Int32> id)
Maybe this works...
Double check that your controller extends ApiController rather than Controller
public class CacheManagementController : ApiController
rather than
public class CacheManagementController : Controller
Related
I am new to WEB API and trying to set up routing for multiple GET actions.
Controller Code
// Get api/values
public IEnumerable<tblUser> Get()
{
//whatever
}
// Get api/values/action
[ActionName("GetByQue")]
public IEnumerable<tblQue> GetQue()
{
//whatever
}
// Get api/values/action
[ActionName("GetUserScore")]
public IEnumerable<tblScore> GetScore(string user)
{
//whatever
}
Config
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional}
);
config.Routes.MapHttpRoute(
name: "DefaultActionApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GetByQue" }
);
config.Routes.MapHttpRoute(
name: "DefaultStringApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GetUserScore" }
);
When I try with http://localhost:54118/api/remote/GetByQue URL getting this error
{
"Message": "The request is invalid.",
"MessageDetail": "The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.String Get(Int32)' in 'HydTechiesApi.Controllers.HydTechiesApiController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."
}
is my routing wrong? Any help would be valuable as I am not able to find a solution.
You should add {action} to routeTemplate instead of {id} in the second configuration
config.Routes.MapHttpRoute(
name: "DefaultActionApi",
routeTemplate: "api/{controller}/{action}",
defaults: new { action = "GetByQue" }
);
also you can try to use route attribure on action :
[ActionName("GetByQue")]
[Route("api/remote/GetByQue")]
public IEnumerable<tblQue> GetQue()
{
//whatever
}
or change order(the second configuration and first configuration) of configuration in WebApiConfig.cs
You made a couple of mistakes in your route. As your example code below:
config.Routes.MapHttpRoute(
name: "DefaultActionApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GetByQue" });
//url: http://localhost:54118/api/remote/GetByQue
(1). routeTemplate: "api/{controller}/{id}". you specify your route has an id and it is not Optional. So your URL must have id. That's what your error showed.you can handle with the issue like below:
defaults: new { id = RouteParameter.Optional }
(2). defaults: new { action = "GetByQue" }); you did not say any thing about action in your routeTemplate. your defaults about action, which does not have any meaning.
(3). From your route, your URL should look like http://localhost:54118/api/remote/5 , you should not get mutiple get method by your route.
There are some solutions, which you may use:
(1). change route like below:
config.Routes.MapHttpRoute(
name: "DefaultActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
please add [HttpGet] in each method just in cause.
[HttpGet]
public IEnumerable<tblUser> Get()
{......}
[HttpGet]
[ActionName("GetByQue")]
public IEnumerable<tblQue> GetQue()
{......}
[HttpGet]
[ActionName("GetUserScore")]
public IEnumerable<tblScore> GetScore(string user)
{......}
Now you can use URL like http://localhost:54118/api/remote/GetByQue
Very Useful Tips: Using [Route("")] tag to specify parameters
**You also have to change Route like above (1)
in Controller, please specify request method to make sure the get method
[HttpGet]
[ActionName("GetUserScore")]
[Route("api/remote/GetScore/{user}")]
public IEnumerable<tblScore> GetScore(string user)
{.....}
//URL: http://localhost:54118/api/remote/GetScore/user
I want to add a new route to my Web Api, which will read various ids and then filter a bunch of books.
So the final url should read something like http://localhost/api/books/1/1/1/1
Now I have added a route to my RouteConfig as follows :-
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "BookFilter",
url: "api/books/{author}/{title}/{genre}/{isbn}",
defaults: new { controller = "Books", action = "BookFilter" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
I also added the following in my BooksController:-
[HttpGet]
public IQueryable<BookDTO> BookFilter(int authorId, int titleId, int genreId, int isbn)
{
//filter books here
return db.Books.ProjectTo<BookDTO>();
}
However when I try to reach the page, I get a 404.
What do I need to do to reach my page?
Thanks for your help and time
Web API and MVC are independent frameworks which each have separate types. The likely reason your route is not working is that you are confusing the two. Specifically, for it to work as you configured, you would need an MVC controller (that is, a controller that inherits System.Web.Mvc.Controller).
So, assuming you want to go with Web API as your question would indicate, you first need to ensure the correct definition of your controller. It should inherit from System.Web.Http.ApiController.
public class BooksController : ApiController
{
[HttpGet]
public IHttpActionResult BookFilter(string author, string title, string genre, string isbn)
{
return Ok("Successful result");
}
}
Next, you need to put your routing in the WebApiConfig.cs file, not in the RouteConfig.cs file. Don't forget to remove your route from the RouteConfig.cs file.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "BookFilter",
routeTemplate: "api/books/{author}/{title}/{genre}/{isbn}",
defaults: new { controller = "Books", action = "BookFilter" });
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
You also need to ensure that the call to GlobalConfiguration.Configure(WebApiConfig.Register); is in your application startup path (by default in Global.asax).
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
Make sure to use the same parameter name in your action (eg. change author to authorId):
Optionally, you can also specify default values for these parameters at your RouteConfig, as follows:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "BookFilter",
url: "api/books/{authorId}/{titleId}/{genreId}/{isbn}",
defaults: new { controller = "Books", action = "BookFilter", authorId= UrlParameter.Optional, titleId = UrlParameter.Optional, genreId = UrlParameter.Optional, isbn = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Controller:
[HttpGet]
public IQueryable<BookDTO> BookFilter(int authorId, int titleId, int genreId, int isbn)
{
//filter books here
return db.Books.ProjectTo<BookDTO>();
}
By testing and wasting obscene amount of time I have found out that ASP.NET MVC has a bug which prevents using the string "api" in request URL. I wan to access my method with URL like this
www.mysite.com/api/test
This is not an unexpected wish. In fact it an an obvious Url choice.
Is there a workaround to achieve this?
UPDATE
By request routing definition.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// this executes, checked it in debugger
routes.MapRoute(
name: "Test",
url: "api/test",
defaults: new { controller = "Api", action = "Test" }
);
}
ApiController
public class ApiController : Controller
{
public ActionResult Test()
{
return Content("TEST TEST TEST");
}
{
If you have the WebApi packages installed, you'll find a WebApiConfig.cs class in App_Start. Here's what it looks like:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
So, assuming you don't change the default code in Global.asax.cs, this route gets added to the routing table. Hence, why your /api/whatever route doesn't work.
If you're not using WebApi, I would suggest removing the packages. Otherwise, you can simply change the "root" part of the API route to something else:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
/* changed 'api' to 'restsvc' */
routeTemplate: "restsvc/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
I use Web API for the first time to connect my mobile app to Web API.
I have MVC 5 project and I have created API folder and inside I have created ProductsController.
namespace DDD.WebUI.API
{
public class ProductsController : ApiController
{
public string GetAllProducts()
{
return "Hello From WebAPI";
}
}
}
And I have tried to access this method from browser:
http://localhost:21879/DDD.WebUI/API/products/GetAllProducts
But I get 404.
It's probably something stupid but can't figure it :(
UPDATE
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "DefaultApi",
url: "API/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
UPDATE 2
I have a little progress:
I have update Application_Start() with:
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
In WebApiConfig I have:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I still store API controller in Project/Api/ProductsController
But now I can access it but still can't get value from it:
http://localhost:21879/api/products/getallproducts
But error I get is:
<Error>
<Message>The request is invalid.</Message>
<MessageDetail>
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.String Get(Int32)' in 'GSL.WebUI.Api.ProductsController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
</MessageDetail>
</Error>
I had the same problem. Watch the order in which you register your routes in Global.asax.cs.
This works:
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
This doesn't:
RouteConfig.RegisterRoutes(RouteTable.Routes);
GlobalConfiguration.Configure(WebApiConfig.Register);
If you use that way of routing, you need to specify the allowed Http methods for the action:
public class ProductsController : ApiController
{
[HttpGet]
public string GetAllProducts()
{
return "Hello From WebAPI";
}
}
You can get all the information regarding API routing from this link: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
I think looking at your update your action requires id which is of non-nullable int and you are not supplying it just modify url as http://localhost:21879/API/products/GetAllProducts/2 here '2' is assigned to 'id' and it will work.
In WebApiConfig modify route as :
config.Routes. MapHttpRoute (
name: "DefaultApi" ,
routeTemplate: "api/{controller}/{action}/{id}" ,
defaults: new { id = RouteParameter . Optional }
);
Just change this Mvc Route :
routes. MapRoute(
name: "DefaultApi" ,
url: "API/{controller}/{action}/{id}" ,
defaults: new { controller = "Home" , action = "Index" , id = UrlParameter .Option
);
with this :
routes. MapRoute(
name: "DefaultApiMvc" ,
url: "APIMvc/{controller}/{action}/{id}" ,
defaults: new { controller = "Home" , action = "Index" , id = UrlParameter .Option
);
because otherwise Mvc and Api routes will conflict with each other...
So I have 2 controller:
[AllowCrossSiteJson]
public class ItemController : ApiController {
[HttpGet]
public LinkedList<Object> FindItem([FromUri]ItemQuery query) {
....
}
}
And
[AllowCrossSiteJson]
public class SubDepController : ApiController
{
[HttpGet]
public LinkedList<Object> FindSubDep(string ID) {
....
}
}
Here is the thing, I try to call both:
http://localhost:43751/api/SubDep
http://localhost:43751/api/Item
And the Item Controller works but the SubDep does not work! Why is that?
Here is my WebApiConfig:
public static void Register(HttpConfiguration config) {
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "withAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
The error I get back is this:
{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:43751/api/SubDep'.","MessageDetail":"No action was found on the controller 'SubDep' that matches the request."}
BONUS question:
How does ASP.NET MVC know that I try to hit:
http://localhost:43751/api/Item
It automatically go to the FindItem Action? It's like pure MAGIC to me!!
When you try to call FindSubDep action, your query string should be like belwo:
http://localhost:43751/api/SubDep/1
For the bonus question. It gets to the correct action because of the HTTP verb [GET] in your case, when you make a GET request for
http://localhost:43751/api/Item
it will find an action with [HttpGet] attribute on Item controller.