I can't figure out why a route is being processed by MVC instead of Web API.
The whole route is configured as follows:
configuration.Routes.MapHttpRoute
(
name: "AdminControllers.TicketingController",
routeTemplate: "api/company/tickets",
defaults: new
{
controller = "Ticketing",
id = RouteParameter.Optional,
action = "GetTickets"
}
);
And the API controller looks like this:
public sealed class TicketingController : ApiController
{
[HttpGet]
public HttpResponseMessage GetTickets()
{
return ControllerContext.Request.CreateResponse(HttpStatusCode.OK);
}
}
I perform a HTTP/GET request to /api/company/tickets and I get the following error:
The controller for path "/api/company/tickets" was not
found or does not implement IController.
Exception's stack trace points to ASP.NET MVC (not Web API): System.Web.Mvc.DefaultControllerFactory.GetControllerInstance(RequestContext requestContext, Type controllerType).
AFAIK, this happens when you map a route using the wrong framework (MVC) instead of Web API HttpConfiguration.Routes.MapHttpRoute extension method. In my sample found above you'll find I'm using the right resource to register a controller in Web API.
I can confirm that the route gets registered during application startup.
The problem is ASP.NET MVC pipeline handles the route and, obviously, it does find no controller for the whole route.
What am I doing wrong here?
NOTE: It's ASP.NET Web API 1.x and I can't use Web API 2.0 (I would love to use attribute routing, yeah).
Update: A pic of registered routes after invoking .MapHttpRoute(...)
Update 2
Believe or not, but it started to work when I changed route configuration to:
configuration.Routes.MapHttpRoute(
name: "AdminControllers.TicketingController",
routeTemplate: "api/company/tickets/{id}",
defaults: new
{
controller = "Ticketing",
id = RouteParameter.Optional,
action = "GetTickets"
}
);
And Web API action changed to:
[HttpGet]
public HttpResponseMessage GetTickets(int? id)
{
return ControllerContext.Request.CreateResponse(HttpStatusCode.OK);
}
It seems like a route parameter makes the route different enough to be ignored by MVC and then processed by Web API pipeline. But I can confirm that there's no other route starting with "api/company/".
Anyway, it works if I give the id (f.e. /api/company/tickets/11). Otherwise, MVC pipeline processes the route...
After looking for solutions, I've got a working one.
First of all, I'm deploying a Web API controller to Windows Azure Pack administrator's site (AdminSite). It's made with ASP.NET MVC 4.0. Anyway, I believe my answer should make sense if you find the same problem I'm going to describe in any ASP.NET MVC and Web API mixed application.
Windows Azure Pack registers this ASP.NET MVC route: {controller}/{action}/{id}: PROBLEM!
*Yes, because it's an URL route that can fit in Web API controller URI scheme...**
So... what?
At the end of the day, this is solved putting Web API routes before MVC ones. This way, ASP.NET will route requests to the first coincidence of some URI route pattern:
// #1 we add the whole routes. One for listing, other for getting
// a single entity (ticket)
configuration.Routes.MapHttpRoute(
name: "Company.AzurePack.Ticketing.List",
routeTemplate: "api/company/tickets",
defaults: new
{
controller = "Ticketing",
action = "GetTickets"
}
);
configuration.Routes.MapHttpRoute(
name: "Company.AzurePack.Ticketing.GetOne",
routeTemplate: "api/company/tickets/{id}",
defaults: new
{
controller = "Ticketing",
action = "GetTicket"
}
);
// #2 we get both added routes from the route table and we create two
// references to them. Later, we remove and re-insert them at the top
// of route table. BINGO!
RouteBase apiRoute1 = RouteTable.Routes[RouteTable.Routes.Count - 1];
RouteBase apiRoute2 = RouteTable.Routes[RouteTable.Routes.Count - 2];
RouteTable.Routes.Remove(apiRoute1);
RouteTable.Routes.Remove(apiRoute2);
RouteTable.Routes.Insert(0, apiRoute1);
RouteTable.Routes.Insert(0, apiRoute2);
Wait, Web API routes are registered into GlobalConfiguration.Configuration.Routes route table...
Yes, but HttpConfiguration.Routes registers its routes in RouteTable.Routes too, and ASP.NET pipeline works with RouteTable.Routes. That is, both MVC and WebAPI routes are in the same route table.
In the following screenshot, you'll find that Web API routes are of type HttpWebRoute:
Now these URIs are served by the fancy Web API controller:
/api/company/tickets
/api/company/tickets/11
An extension method to make our life even easier!
After I checked that this solution was working, I refactored above sample code to an extension method:
public static class HttpRouteCollectionExtensions
{
public static IHttpRoute MapHttpRouteAt(this HttpRouteCollection routes, int index, string name, string routeTemplate, object defaults = null, object constraints = null, HttpMessageHandler handler = null)
{
Contract.Requires(routes != null);
IHttpRoute route = routes.MapHttpRoute(name, routeTemplate, defaults, constraints, handler);
RouteBase apiRoute = RouteTable.Routes[RouteTable.Routes.Count - 1];
RouteTable.Routes.Remove(apiRoute);
RouteTable.Routes.Insert(index, apiRoute);
return route;
}
}
...and now I'm able to add routes at the top of routing table as follows:
configuration.Routes.MapHttpRouteAt(
index: 0,
name: "sampleRoute",
routeTemplate: "api/some/path/{name}",
defaults: new
{
controller = "Some",
action = "SomeAction",
httpMethod = new HttpMethodConstraint("GET")
}
);
Related
I've got an API app up and running on my localhost. It works great at en endpoint such as:
http://localhost:26307/api/ModelName/12345
However, after deploying to the Test server, the same url needs to be:
http://v10test.mydomain.com/api/api/ModelName/12345
I'm trying to get rid of the double /api/.
I adjusted my routes FROM THIS (for example):
config.Routes.MapHttpRoute(
name: "route2",
routeTemplate: "api/{controller}/{organizationSys}"
);
TO THIS.....
config.Routes.MapHttpRoute(
name: "route2",
routeTemplate: "{controller}/{organizationSys}"
);
NOTE: I REMOVED "api/" FROM THE ROUTES.
But that seemed to have no effect. In IIS, the api's are set up as an Application within the domain:
I'm probbaly overlooking something obvious, but I'm just not sure what. How do I get rid of that double /api/?
There are several ways to specify routes to a controllers actions, and the order of precedence matters.
Firstly, it can be done from a general configuration, e.g. as you've done it...
config.Routes.MapHttpRoute(
name: "route2",
routeTemplate: "{controller}/{organizationSys}"
);
However this can be overridden by specifying a Route attribute on the controller or by specifying the route in the verb attribute. For example in the code below...
[Route("api/[controller]/[action]")]
public class TestController : Controller
{
[HttpGet]
[Route("/api")] // url... /api
[Route("/api/test")] // url... /api/test
[Route("testalso")] // url... /api/test/get/testalso
public string Get()
{
return "Alive";
}
[HttpGet("/api/echo/{id}")] // url... /api/echo/{id}
public string Echo(string id)
{
return $"Get Echo: {id}";
}
[HttpPost("{id}")] // url... /api/test/postit/{id}
public string PostIt(string id)
{
return $"Thanks for {id}";
}
}
The declaration on the controller specifies a default for all methods within the controller and any methods that specify attributes can either override the controller by starting the route with a '/' or append to the controller's route. See the comments next to each attribute above for examples.
Beyond that the routes will be relative to the base application hosted within iis in your case which starts at...
http://v10test.mydomain.com/api/
Hope that's enough information for you.
It is because of the way it is deployed in IIS. For IIS your application is at address
http://v10test.mydomain.com/api/
And after that comes all the controllers and etc. If you deploy it to (for example)
http://v10test.mydomain.com/myapp/
the calls will work like
http://v10test.mydomain.com/myapp/api/ModelName/12345
EDIT
Also - check the routes of your controllers or methods inside the controllers. This is the other place where the route may be modified/extended.
So I have a HomeController, to access it along with Actions I have to type url.com/home/action.
Would it be possible to change this to something else like url.com/anothernamethatpointstohomeactually/action?
I suggest you to use attribute routing, but of course it depends on your scenario.
[Route("prefix")]
public class Home : Controller {
[HttpGet("name")]
public IActionResult Index() {
}
}
This will be found at url.com/prefix/name
There are a lot of options to attribute routing, some samples:
[Route("[controller]")] // there are placeholders for common patterns
as [area], [controller], [action], etc.
[HttpGet("")] // empty is valid. url.com/prefix
[Route("")] // empty is valid. url.com/
[HttpGet("/otherprefix/name")] // starting with / won't use the route prefix
[HttpGet("name/{id}")]
public IActionResult Index(int id){ ... // id will bind from route param.
[HttpGet("{id:int:required}")] // you can add some simple matching rules too.
Check Attribute Routing official docs
You can add new Routes in your Startup.Configure method within your app.UseMvc(routes => block:
routes.MapRoute(
name: "SomeDescriptiveName",
template: "AnotherNameThatPointsToHome/{action=Index}/{id?}",
defaults: new { controller = "Home"}
);
The code is quite similar to ASP.NET MVC.
For more info, see Routing in ASP.NET Core.
Below is for ASP.NET MVC (not ASP.NET Core MVC)
You can also add a new Route via routes.MapRoute in your RouteConfig:
routes.MapRoute(
name: "SomeDescriptiveName",
url: "AnotherNameThatPointsToHome/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Make sure, you insert the code before you define your Default route.
For more information, visit the docs.
Using the Route attribute on top of your controller will allow you to define the route on the entire controller.
[Route("anothernamethatpointstohomeactually")]
You can read more here.
In ASP.NET Core 6, we just do that in one line.
Go to your Controller and write before your action method:
[Route("YourController/YourAction/{YourParameter?}")]
In your example, you you need to write like this:
[Route("Home/Index/{name?}")]
You can change your url by modifying your routing configuration.
It is kind of like htaccess but not really.
https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/creating-custom-routes-cs
Another solution is to create a page and do a server redirect.
Server.Transfer
I have two controllers which contain different action name, I am trying to access them, I am able to access PrepareTransmittalLetterPath under Transmittal controller but not able to access PrepareConcreteReport under Concrete controller.
May be my web api route is not working properly. Thanks in advance.
Transmittal Controller--where TransmittalApi is a class
public string PrepareTransmittalLetterPath(TransmittalApi transmittalApi)
{
}
Concrete Controller- where ConcreteReportApi is a class
public ConcreteReportApi PrepareConcreteReport(ConcreteReportApi concreteApi)
{
}
WebApiConfig-
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "Api",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Using convention-based routes means your action method names need to start with Get.... or Post.....
To find the action, Web API looks at the HTTP method, and then looks for an action whose name begins with that HTTP method name. For example, with a GET request, Web API looks for an action that starts with "Get...", such as "GetContact" or "GetAllContacts". This convention applies only to GET, POST, PUT, and DELETE methods. You can enable other HTTP methods by using attributes on your controller.
See Routing in Asp.Net Web API for more information.
I have been building a WebAPI, trying to route to the right methods with ActionName. It works with one of my methods I try to call, but the other one gets a 404 error.
My WebAPI Config file:
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.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
My WebAPI Controller methods are formatted as such:
This first one is the working one:
[ActionName("postdb")]
public IEnumerable<string[]> postDB(string id)
{ ...
This second one does not:
[ActionName("getquery")]
public IEnumerable<string[]> getQuery(string tables)
{ ...
I'm calling both of them the same way from angular (Temp is a string that is being passed as the argument):
$http.post('api/Test/postdb/' + temp).then(function (response) { ...
and
$http.get('api/Test/getquery/' + temp).then(function (response) { ...
I have tried changing names of both actions, the first one works no matter the name, the second one doesn't work no matter the name. I have also tried reordering them, changing between GET and POST, and changing arguments.
Any suggestions?
Not sure why you are using ActionName to setup routing?
You should probably be looking at Route attribute. eg.
[HttpPost]
[Route("postdb")]
// Action doesn't have to be called 'postdb'
public IEnumerable<string[]> postDB(string id)
ActionName is usually used for a different purpose (Purpose of ActionName)
Nevertheless, I think something odd is going on in your example - I'd think setting ActionName shouldn't have affected routing there. To debug I'd suggest to set up Failed Request Tracing to see at which point the request fails to reach the action.
These are the basic rules for Action selection in WebAPI (http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection)
You can specify the HTTP method with an attribute: AcceptVerbs, HttpDelete, HttpGet, HttpHead, HttpOptions, HttpPatch, HttpPost, or HttpPut.
Otherwise, if the name of the controller method starts with "Get", "Post", "Put", "Delete", "Head", "Options", or "Patch", then by convention the action supports that HTTP method.
If none of the above, the method supports POST.
So, in your example postdb method may map to the POST method. But may be because it's in lower case ASP.NET didn't like that and applied Rule 3 - try with ActionName("PostDB") and [ActionName("GetQuery")] if you really want to use ActionName (for whatever reason) instead of Route.
The name of the parameter tables in the second action
[ActionName("getquery")]
public IEnumerable<string[]> getQuery(string tables)
{ ...
does not match the name of the parameter id in the route:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
An existing project has controllers that inherit from either:
Controller: RouteTable.Routes.MapRoute with "{controller}/{action}/{id}".
ApiController: GlobalConfiguration.Configure and in the callback MapRoute with "api/{controller}/{id}".
Everything works fine, but I need to generate URLs for action methods in both of these types of controllers. Given:
a name or type of a controller that inherits from either of these, and
an action method name
Then from the web site side, how can I generate proper URLs for the web API side?
I'm using reflection to get action and controller names right now, and then through using UrlHelper.Action(actionName, controllerName, routeValueDictionary) am getting the correct URL for web site routes.
However, this method is (of course) generating URLs like this for the WebAPI side: /ApiControllerName/Get?parameter1=value when it needs to be /api/ApiControllerName?parameter1=value and the separate knowledge that it's a GET request.
Purpose: this is for a smoke test page for the web site that uses attributes and reflection to decide what to smoke test. It would be nice to be able to use the same attribute throughout the project, and furthermore it would be very nice to be able to use the correct UrlHelper that is aware of the routing tables and can produce the right prefix such as /api/, instead of the code assuming, perhaps wrongly, that the API routes were registered with api and not, say, webapi.
Update
After continued research I have found the Url.HttpRouteUrl method which can generate WebAPI URLs, but this requires knowing a route name, not an action method name.
I've done even more research on this and haven't gotten any closer to a solution. It looks like if you know the route name of the appropriate route, you can cook up a Url easily. There are also some likely hints here and here. But if there are multiple routes for WebApi, how do you know which one matches the controller and action you want? It would be dumb to reimplement what MVC itself already does in selecting a controller and action. I guess I could construct a URL from the given parameters using every WebApi route, then run the URL through its paces (using some of the above links) and see if it is going to match the desired controller... yuck.
There's got to be an easier way.
For now I'm going to have to move on, but here's to hoping someone can help me out.
Here a few ways of do it:
Using RouteUrl() method:
var url1 = Url.RouteUrl(new { id = 1, controller = "...", httproute = true });
The trick is of course httproute = true. Setting this property, you inform that you only want http routes (Web Api routes).
Using HttpRouteUrl() method:
var url2 = Url.HttpRouteUrl(null, new { id = 2, controller = ".." });
And another way to do it using directly the routes and httproute value:
var values = new RouteValueDictionary(new
{
id = 3,
controller = "...",
httproute = true
});
var url3 = RouteTable.Routes.GetVirtualPath(Request.RequestContext, values).VirtualPath;
The 3 ways are basically the same. Since you don't specify the route, the system will try to find the first match according to the route values. For example, lets say you have an ApiController called RestfulController and web api routes are configured like so:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Using the first method, when you do var url = Url.RouteUrl(new { id = 123, controller = "restful", httproute = true }); in a MVC Controller, the value in url is /api/restful/123.
But if you add a new route ConstraintApi:
config.Routes.MapHttpRoute(
name: "ConstraintApi",
routeTemplate: "api2/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { controller = "restful" }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
The url returned by RouteUrl is /api2/restful/123.
You should be aware the order when you are declaring your routes. If the route DefaultApi is added before the route ConstraintApi, the url generated is /api/restful/123.
There is the Hyprlinkr library which allows you to create URIs from the Controller and Action using the route configuration.
This seems more like a hack, but worth a try if you only plan to use it for testing. As you mentioned, you can generate a URL with a route name. So you could grab all route names and generate all possible URLs. Then you need to check if the URL resolves to the action that you need or not. For that you can use something like the following: https://stackoverflow.com/a/19382567/1410281. The basic idea is that if you've got a URL, then do a fake redirect there, and get the routing data from the framework.