WebAPI MapHttpRoute doesn't work, but AttributeRouting does - c#

I have a case where I can't get a default route to work via Config.Routes.MapHttpRoute(), but if I put the route as a route attribute in the controller it works fine.
Global.asax
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
//RouteConfig.RegisterRoutes(RouteTable.Routes);
//BundleConfig.RegisterBundles(BundleTable.Bundles);
}
(removed last two as they're non-WebAPI requirements (right?) same result even if I leave them in)
WebApiConfig.cs
public static void Register(HttpConfiguration config) {
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "v1/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Controller
public class ErrorsController : ApiController
{
[HttpGet]
[Route("v1/Errors/Get")]
public IHttpActionResult Get(int id) {
return Ok();
}
[HttpPost]
[Route("v1/Errors/Submit")]
public IHttpActionResult Submit()
{
// do stuff
return Ok();
}
}
If I have the attribute routes in there, everything works fine. If I don't, I get 404s. For example, the following two report a 404 error:
localhost:myport/v1/Errors/Get?id=5
localhost:myport/v1/Errors/Submit
Yet, if I submit this:
localhost:myport/v1/Errors
I get the following response:
No HTTP resource was found that matches the request URI
'http://localhost:59498/v1/Errors/'.
No type was found that matches the controller named 'v1'.
Obviously my route configuration isn't kicking in, but for the life of my I can't tell why. I even tried changing the route name to Default instead of DefaultApi, thinking that perhaps Default had some internal significance.

"Normal" routing doesn't support this kind of routes with prefix "v1" in it. In a way or another this kind of rounting follows the old MVC routing, where the first part of the address must be the controller; so when you set the address localhost:myport/v1/Errors the system is currently looking for a controller named "v1".
If you want to use a route with a prefix before you have to stick with the attribute routing; that's why it works perfectly with attributes and it doesn't without.
May I suggest you to use a "global" prefix for the v1 thing? That would allow you to avoid repeating the same part of the URL over and over again on different resources. I suggest you to check this article for a couple of implementation details.

Related

Cannot get ASP.NET Web API routing to work at all

I am trying to refactor an old setup, the end goal will be that I will have a functional REST API. I am getting rid of an old NuGet service and I sort of have to rebuild everything now. I am trying to set it up using ASP.NET Web API. This will include 60+ routes (like "www.website.com/cars/{id}/engine" "www.website.com/inventory/{id}" etc..)
I have tried attribute routing and conventional routing, and nothing seems to work. I get 404's no matter what I seem to try. I am probably just not doing either of them correctly.
Here is how I am setting up configuration (I am using a self hosting package btw but this is the main configuration point):
public class SomeHostClass
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
//If I am trying attribute based routing, uncomment this
//config.MapHttpAttributeRoutes();
Register(config.Routes);
// I think I need this at all times?
app.UseWebApi(config);
}
public void Register(HttpRouteCollection routes)
{
// One of many other routes.MapHttpRoute() calls
routes.MapHttpRoute(
name: "Cars",
routeTemplate: "cars/{id}/engine",
defaults: new
{
controller = "CarController",
action = "Get"
},
constraints: null);
}
}
Then here is my Car Controller:
public class CarController : ApiController
{
[HttpGet]
public object Get([FromUri] CarDTO carObject)
{
// Some code calling a private worker method
return WorkerMethod();
}
private object WorkerMethod()
{
// Worker method do stuff, return
}
}
This Get() method is never called and a 404 is returned.
Another note: I have tried using reflection to register all the routes, which slims down the code. I'll debug it and it looks like all the routes have been configured correctly within the HttpConfiguration.routes but I will get 404's still. Even without reflection doing it the way shown above- if I debug and look at the values they all seem correct, but don't work.
Another Note: I should also mention that this is the error I usually get along with the 404.
{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:8000/cars/1/engine'.","MessageDetail":"No type was found that matches the controller named 'CarController'."}
How do I get these routes recognized and stop returning 404's and start returning my data?
If this is a bad way of going about this- what's the best way?

Web Api 2 Routing Issue: "No action was found on the controller..."

I am having the issue of getting a 404 on my web api routing. Although there are many posts about this issue, they mainly seem to be about changing the order of the routes so that the MVC routes don't override the api routes.
I have tried all of the solution I have come across yet nothing seems to fix my issue.
Here is my controller:
[RoutePrefix("api/paving-designer")]
public class PavingDesignerController : ApiController
{
[HttpGet]
[Route("get-forms/{userId}")]
public IHttpActionResult GetForms(Guid userId)
{
ICollection<PavingDesignerFlatForm> forms = _helper.GetForms(userId);
if (forms != null)
{
return Ok(forms);
}
else
{
return NotFound();
}
}
}
And this is my Web Api Config
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new
{
id = RouteParameter.Optional
});
}
}
and this is my global asax
private void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
// RouteConfig.RegisterRoutes(RouteTable.Routes);
// Initialize Castle & install application components
_bootstrapper = CastleInitialiser.Initialise();
}
As you can see I have even tried to comment out the mvc routes to see if that made a difference
If I browse to http://localhost/api/paving-designer/get-forms/c6c489a7-46c6-420e-9e39-56797c8094cf
I get the following error:
No type was found that matches the controller named 'paving-designer'.
I have tried changing the route prefix to the following but to no avail
/api/paving-designer
/paving-designer
paving-designer
And if I browse to http://localhost/api/pavingdesigner/get-forms/c6c489a7-46c6-420e-9e39-56797c8094cf
I get the following error:
Multiple types were found that match the controller named 'pavingdesigner'. This can happen if the route that services this request ('api/{controller}/{action}/{id}') found multiple controllers defined with the same name but differing namespaces
I don't see how I can have multiple controllers as this is the only one I have.
Can anyone see where I am going wrong?
You are using both routing types.
Using attribute routing defined next route:
/api/paving-designer/get-forms/{userId}
Using default routing there is other route:
/api/{controller}/{action}/{id}
These routes are have the same template.
But using the first of them - ControllerSelector can not find paving-designerController.
Using the second - there is no action named get-forms. There are GetForms
If you remove one of them - it should work.
Ok in my particular case, the error was being caused as my IoC was registering the controller twice.
This was causing the duplicate entries, which in turn made the attribute routing fail.

C# web api route for a webservice

I am coding a web api in c# and I have a question in regards to the correct route to access a function called test.
Here is the class definition:
[RoutePrefix("api")]
public class ItemsWebApiController : ApiController
I have a RoutePrefix as follows:
[Route("test")]
Here is the function called test:
[Route("test")]
[System.Web.Http.AcceptVerbs("GET")]
[System.Web.Http.HttpGet]
public String test()
{
return "test";
}
I am trying to access the following url:
http://localhost/api/test
The above url is displaying the following exception:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its
dependencies) could have been removed, had its name changed, or is
temporarily unavailable. Please review the following URL and make
sure that it is spelled correctly.
Requested URL: /api/test
How can I access the test function, such that the string "test" is displayed in my browser?
EDIT
I have deployed to my local IIS, and the database connection strings are working correctly.
The address for the local IIS is http://localhost/
These are the urls that I have tried:
http://localhost/test
http://localhost/api/test
http://localhost/api/test/test
http://localhost/ItemsWebApiController/test
http://localhost/ItemsWebApi/test
All of the above return the error page.
Thanks
If you a putting [Route("test")] your url will be http://address/test
if you need url like http://address/api/test, change your route like [Route("api/test")]
Note: you need to add [HttpGet] as well
You have to activate Attribute Routing in WebAPI Controllers
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
and in your application start
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
And then your URL is http://localhost/test, because the default route would not match here.
The route based on your configuration will be: http://address/api/test/test
the first test is the route prefix from the attribute [Route("test")];
the scond test is the action in the controller from the attribute defined on the method
[Route("test")]
public String test()
{
Web api infers the http method based on your action name. It won't know from "Test" what to use.
See the docs
HTTP Methods
Web API also selects actions based on the HTTP method of the request (GET, POST, etc). By default, Web API looks for a case-insensitive match with the start of the controller method name. For example, a controller method named PutCustomers matches an HTTP PUT request.
You can override this convention by decorating the mathod with any the following attributes:
[HttpDelete]
[HttpGet]
[HttpHead]
[HttpOptions]
[HttpPatch]
[HttpPost]
[HttpPut]
The following example maps the CreateBook method to HTTP POST requests.
[Route("api/books")]
[HttpPost]
public HttpResponseMessage CreateBook(Book book) { ... }

WebApi 2 Route Attribute not working unless using parameter

I have a controller List
[Route("api/cache/list")]
[HttpGet]
public IEnumerable<string> List()
{
...
}
But i get a 404 if i try to go to localhost:12121/api/cache/list. My webapi config looks like this:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
//config.SuppressDefaultHostAuthentication();
//config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Formatters.Remove(config.Formatters.XmlFormatter);
//config.Routes.MapHttpRoute(
// name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
// defaults: new { id = RouteParameter.Optional }
//);
}
}
On the other hand if i change that controller to
[Route("api/cache/list/{id}")]
[HttpGet]
public IEnumerable<string> List(int id =0)
{
...
}
It will work. Ive tried doing it without the id as a parameter, still doenst work. What am I doing wrong here?
Extra Info:
I do have a strange setup. My WebApiConfig.cs and global.cs are in a different project. The project which has my controller will then reference the project with the WebApiConfig.cs
Rather than giving the param a default value, try making your id param nullable:
public IEnumerable<string> List(int? id)
Then check for null in code. This also helps differentiate a passed zero from an omitted param value.
I wasnt able to figure out how to fix the problem exactly but I did find a way around my problem.
First I correctly identified that when my routing was in a single assembly the exact same code worked fine. When the routing for my project in a different assembly makes it difficult for config.MapHttpAttributeRoutes(); to correctly identify attribute routes.
On the other hand using a RoutePrefix seemed to fix my issue.
Im not going to set this as my answer because I feel it doesn't truly address the problem, but I felt it would be informational to anyone who comes along this question.

Using attribute routing and convention based routing with Web API and MVC simultaneously

I have an Asp.net MVC web application that uses convention based routing. I recently added some Web Api 2 controllers, for which I used attribute routing. Despite the documentation claiming that you can use both, I can either get the (attribute routed) API methods to work, or the (convention routed) web application methods.
This is RouteConfig.RegisterRoutes():
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Tables", action = "Index", id = UrlParameter.Optional },
namespaces: new string[] { "Foo.Cms.Controllers" }
);
}
This is WebApiConfig.Register():
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
// The models currently only serialize succesfully to xml, so we'll remove the json formatter.
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.JsonFormatter);
}
And this is Application_Start():
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
}
This way, only the routing to the web api controllers works. If I switch GlobalConfiguration.Register() and RouteConfig.RegisterRoutes(), like so:
RouteConfig.RegisterRoutes(RouteTable.Routes);
GlobalConfiguration.Configure(WebApiConfig.Register);
...only the convention-based routing works.
I'm at a loss. What's going on here?
Edit:
What I'm trying to achieve:
The application currently uses the basic {controller}/{action}/parameters convention. So I have a controller called ElementsController that has, for instance, an Index() method that is routed to /Elements or a ListPublic() method that is routed to /Elements/ListPublic. I achieved this with the convention based routing mentioned above.
I also have a bunch of Web Api controllers (for instance, TablesController) that I want to route to using a /api/v0/tables route. I tried to achieve this like so:
[RoutePrefix("api/v0/tables")]
public class TablesController : ApiController
{
[Route()]
public string Get()
{
// ...
}
}
As you can see, it's not the same route pattern: api calls are all prefixed with api/v0/. For some reason though, it still appears to treat them as the default {controller}/{action} routes.
What's occurring is that the "first registered" route is taking effect. If I have a MVC route defined as
{controller}/{action}/{id}
and a Web API route defined as
{controller}/{action}/{id}
The first registered route will take effect.
Why is this the case? Imagine you send a request to the server
foo/bar/1
Which route does this match?
Both!
It will choose the first result that matches the route regardless of the type of routing used. If you want some examples as to how to make these routings work, check out this link
If an URL matches two different route templates, it doesn't matter if they refer to MVC or Web API, the only rule is that the first registered route will be used to select the action to execute.
In your particular case, if you make a GET request to this URL: /api/v0/tables, the action selector starts checking the registered routes.
The first route that you're registering is an MVC route like this: /{controller}/{action}/{id}. So, the template matches with this values:
controller = "api"
action = "v0"
id = "tables"
If you register the attribute routes before the MVC route, the first matching route template will be your route attribute template, so the Web API controller action will be correctly selected.
In other words, if you need to route Web API and MVC in the same application you have to use routes which match different URLs for each action, and be careful with the order in which they are registered: register first the more specific templates, and then the more generic ones, so that the generic templates doesn't swallow the URIs which should be matched with the specific templates.
This is not the only option. You can also use route constraints, for example, if all your API has a particular naming convention which can be checked with a constraint (for example a Regex constraint), you can still use convention routing for Web API.
NOTE: apart from the route matching, it's also necessary that the HTTP method (like POST, GET, etc.) is supported by the action. So you can have two similar actions in the same route that accept different methods, and the Action selector would know which one to choose, but this doesn't solve your problem

Categories

Resources