I am trying to configure a specific route to execute a message handler before it hits the controller, however the message handler is never executed.
I have the following controller:
public class TestController : ApiController
{
[Route("private/users")]
public IHttpActionResult Get()
{
return Ok();
}
}
And the following route configuration:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "private/users",
defaults: new { id = RouteParameter.Optional },
constraints: null,
handler: new TokenValidationHandler() { InnerHandler = new HttpControllerDispatcher(config) }
);
And The MessageHandler looks like this:
public class TokenValidationHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//some logic to validate token
return base.SendAsync(request, cancellationToken);
}
}
The TokenValidationHandler is never executed which seems to be because of the routeTemplate when configuring the route. Is it possible to achieve route specific configuration with a hardcoded routeTemplate like "private/users" instead of the default "{controller}/{id}" format?
What I want to do is have my TokenValidationHandler fire for all of my private api calls and have public api calls bypass the TokenValidationHandler.
Attribute routes a matched before convention-based routes. The action in question is tagged with an Route attribute. If attribute routing is enabled then it means that it will not reach the convention-based route and thus not invoke the handler.
Remove the route attribute
public class TestController : ApiController {
public IHttpActionResult Get() {
return Ok();
}
}
The order of route registration also plays a factor for convention-based routes so make sure that general routes are registered after more specific routes
config.Routes.MapHttpRoute(
name: "PrivateApi",
routeTemplate: "private/users",
defaults: new { id = RouteParameter.Optional },
constraints: null,
handler: new TokenValidationHandler() { InnerHandler = new HttpControllerDispatcher(config) }
);
//more general route
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
//...
);
Otherwise if attribute routing is your preference you can consider using an action filter.
Based on stated intentions you would be better off using Authentication Filters in ASP.NET Web API 2
Related
I'm using attribute routing in a Web API controller (.NET Framework 4.5.2 - don't ask, I'm trying to get approval for time to move everything forward).
I have applied a [RoutePrefix] attribute to my ApiController.
I have two controller actions, both HttpGets. Each has a [Route] attribute applied.
I'm using Swagger to auto-generate docs. In the docs for this controller I see three endpoints listed - two for my controller actions, and another HttpGet with the bare controller route.
That is what I have is this:
[RoutePrefix("api/test/Tickets")]
public class TestTicketsController : ApiController
{
[HttpGet, Route("")]
public HttpResponseMessage GetTickets()
{
....
}
[HttpGet, Route("since")]
public HttpResponseMessage GetTicketsSince(string since)
{
....
}
}
And In the generated Swagger docs I see three endpoints:
GET api/test/Tickets
GET api/test/Tickets/since
GET api/TestTickets
This third endpoint, api/TestTickets, seems to be derived from the class name of the controller, ignored my routing attributes. And when I call it, I get an HTTP 200 with an empty body, despite not having defined an action for it.
Where is this coming from? And how can I stop it from being generated?
===
It was suggested that I remove the [HttpGet, Route("")] attribute. If I do, I get an error:
Multiple operations with path 'api/TestTickets' and method 'GET'.
It was also suggested that I include my WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "swagger_root",
routeTemplate: "",
defaults: null,
constraints: null,
handler: new RedirectHandler((message => message.RequestUri.ToString()), "api/docs/index"));
var mediaType = new MediaTypeHeaderValue("application/json");
var formatter = new JsonMediaTypeFormatter();
formatter.SupportedMediaTypes.Clear();
formatter.SupportedMediaTypes.Add(mediaType);
config.Formatters.Clear();
config.Formatters.Add(formatter);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
If I comment-out the config.Routes.MapHttpRoute, the extra endpoint goes away.
Now it's just a matter of determining whether we have any controllers that expect this default endpoint to be there.
Thanks.
abdulg pointed out in a comment, rather than in an answer - default routes are configured in WebApiConfig.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I have two end points:
api/v1/user/session (For creating user login with post request )
api/v1/user (For creating user with post request)
How to route this two endpoints in same controller? I also want to specify action for a specific request. More clearly:
all get,post,update, patch operations can be done in api/v1/user/session endpoint
all get,post,update, patch operations can be done in api/v1/user endpoint
Is it possible ?
Example:
config.Routes.MapHttpRoute(
"UserApi",
"api/v1/{controller}/session",
new { controller = "User", action="Session" });
Now, I want all rest requests to work for Session method with [httpPost],[httpGet] etc attributes.
config.Routes.MapHttpRoute("lol", "api/v1/{controller}/session",
new { controller = "User", action="Session" });
//config.Routes.MapHttpRoute(
// name: "LoginApi",
// routeTemplate: "api/v1/{controller}",
// defaults: new { controller = "User"}
//);
config.Routes.MapHttpRoute(
name: "RailStationApi",
routeTemplate: "api/v1/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I would suggest you look at attribute routing - this is a lot easier to specify than using the central configuration.
[RoutePrefix("api/v1")]
public class UserController : ApiController {
[HttpPost]
[Route("user/session")]
public void Login(/*...*/) {
// ...
}
[HttpGet]
[Route("user/session")] // Note this has the same route as Login
public SessionResult GetSession(/*...*/) {
// ...
}
[HttpPost]
[Route("user")]
public void CreateUser(/*...*/) {
// ...
}
}
Note that you don't technically need [HttpPost] since it is the default, but I included it for clarity. You can add methods with the other Http verbs in the same way.
I tried with hardcoded solution and it worked. I added following route into the webapi.config file and it worked.
RouteTable.Routes.MapHttpRoute(
name: "SessionApi",
routeTemplate: "api/v1/user/session",
defaults: new { Controller = "Session", id = RouteParameter.Optional }
).RouteHandler = new SessionStateRouteHandler();
I'm currently building an API using Web API 2.2
I have the RESTful part of it working but now I need one non-RESTful controller:
public class PremisesController : ApiController
{
private PremiseService _service;
public PremisesController()
{
_service = new PremiseService();
}
[HttpGet]
public IHttpActionResult Premise(string id)
{
id = id.Replace(" ", String.Empty).ToUpper();
List<Premise> premises = _service.GetPremisesForPostcode(id);
return Ok(premises);
}
[HttpGet]
public IHttpActionResult Building(string id)
{
double premise = Convert.ToDouble(id);
Building building = _service.GetBuildingsForPremise(premise);
return Ok(building);
}
}
The routing config is as follows:
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 }
);
Im getting the error that it can't distinguish between the two methods when I initiate a GET action:
Multiple actions were found that match the request
So my question is Do I need to specify the Route attribute on top of each method and if yes, why? Doesn't the second route (ActionApi) deals with that situation?
EDIT:
I just tested you're code and it works the way it is... maybe just it is unclear.
/api/Premises/Premise/8 --> will take you to your first action
/api/Premises/Building/8 --> will take you to your second action
/api/Premises/8 --> will cause error because the routing will go to the first rule api/{controller}/{id} with a GET request, then he can't distinguish which of the actions you want because they both match the first route: (api/Premises/{id})
You could also use the RoutePrefix attribute on your controller.
[RoutePrefix("api/premises")]
public class PremisesController : ApiController
That combined with the route attribute would mean you shouldn't get multiple actions with the same route
I have these two routes defined:
routes.MapRoute(
name: "GetVoucherTypesForPartner",
url: "api/Partner/{partnerId}/VoucherType",
defaults: new { controller = "Partner", action = "GetVoucherTypesForPartner"}
);
routes.MapRoute(
name: "Default",
url: "api/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional}
);
In my PartnerProfile controller, I have 2 methods:
public Partner Get(string id)
{ }
public IEnumerable<string> GetVoucherTypesForPartner(string id)
{ }
If I hit the url ~/api/Partner/1234 then, as expected, the Get method is called.
However, if I hit the url ~/api/Partner/1234/VoucherType then the same Get method is called. I am expecting my GetVoucherTypesForPartner to be called instead.
I'm pretty sure something in my route setup is wrong...
You seem to have mapped standard MVC routes, not Web API routes. There's a big difference. The standard routes are used by controllers deriving from the Controller class, but if you are using the ASP.NET Web API and your controllers are deriving from the ApiController type then you should define HTTP routes.
You should do that in your ~/App_Start/WebApiConfig.cs and not inside your ~/App_Start/RouteConfig.cs.
So go ahead:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "GetVoucherTypesForPartner",
routeTemplate: "api/Partner/{partnerId}/VoucherType",
defaults: new { controller = "Partner", action = "GetVoucherTypesForPartner" }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
and then:
public class PartnerController : ApiController
{
public Partner Get(string id)
{
...
}
public IEnumerable<string> GetVoucherTypesForPartner(string partnerId)
{
...
}
}
Things to notice:
We have defined HTTP routes not standard MVC routes
The parameter that the GetVoucherTypesForPartner action takes must be called partnerId instead of id in order to respect your route definition and avoid any confusions
Here is the routing configuration in WebApiConfig.cs:
config.Routes.MapHttpRoute(
name: "DefaultApiPut",
routeTemplate: "api/{controller}",
defaults: new { httpMethod = new HttpMethodConstraint(HttpMethod.Put) }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { httpMethod = new HttpMethodConstraint(HttpMethod.Get, HttpMethod.Post, HttpMethod.Delete) }
);
Here is my controller:
public class MyController : ApiController {
[HttpPut]
public void Put()
{
//blah
}
}
Somehow when the client sents the PUT request with the URL /api/myController/12345, it still maps to the Put method in MyController, I am expecting an error like resource not found.
How to force the Put method only accept the request without a parameter?
Thanks in advance!
This works to constrain http method on routes:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "LocationApiPOST",
routeTemplate: "api/{orgname}/{fleetname}/vehicle/location",
defaults: new { controller = "location" }
constraints: new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) }
);
config.Routes.MapHttpRoute(
name: "LocationApiGET",
routeTemplate: "api/{orgname}/{fleetname}/{vehiclename}/location/{start}",
defaults: new { controller = "location", start = RouteParameter.Optional }
constraints: new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) }
);
...
}
You're putting your httpMethod constraint into defaults, but it should be in constraints.
defaults just says what the default values will be if the request doesn't include some or all of them as routing parameters (which in the case of the verb, is meaningless, since every HTTP request always has a verb as part of the protocol). constraints limit the combination of route values that will activate the route, which is what you're actually trying to do.
FYI, for this simple/standard routing, you don't need the [HttpPut] attribute in an API controller either. That's already handled by the HTTP routing which maps the verb to the controller method.