I am setting up an ASP.NET MVC 4 Web API to accept requests from a 3rd party server, and I simply can't figure out how to set the route mappings.
Assume the 3rd party server expects to get responses for:
http://[my_server]/authorize?user=[user name]&session=[session token]&item=[item]
http://[my_server]/release?user=[user name]&session=[session token]
Alternatively, the requests can use a dedicated path, i.e.:
http://[my_server]/***api***/authorize?user=[user name]&session=[session token]&item=[item]
http://[my_server]/***api***/release?user=[user name]&session=[session token]
I would like to be able to support both alternatives.
Additional requests, following the more traditional /controller/id form, should be implemented too, but I'd like to focus on the above (I'm not even sure that Web API is the way to go here).
I have written the following controller:
public class MyController : ApiController
{
[HttpGet]
[ActionName("authorize")]
public string Authorize(string user, string session, string item)
{
...
// return "OK" or "DENY";
}
[HttpGet]
[ActionName("release")]
public string Release(string user, string session)
{
...
return "OK";
}
}
and tried everything I could find in SO and elsewhere in WebAppConfig.Register, but I keep getting a 404 error when I try the request in the browser:
http://localhost:22332/api/authorize?user=ury&session=token&item=an_item
My question is, what do I have to do - specifically in WebAppConfig.Register and in the controller - in order to serve the above requests (assuming my test URL is correct...)?
You're getting tripped up by Web API conventions for matching a controller name in a URL to a controller class.
If the name of your controller is "MyController", then the URL to request is:
http://localhost:22332/api/my/authorize?user=ury&session=token&item=an_item
To support a request w/o the "api" bit in the URL, simply add a second route definition in the Register method of WebApiConfig.cs. Since you're also using "actions" in your URL's, you'd need these two routes:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "RootWebApiRoute",
routeTemplate: "{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
If you want to omit the "api" prefix as well as the controller parameter, I think you need a different route definition. Something like:
public static void Register(HttpConfiguration config) {
config.Routes.MapHttpRoute(
name: "ActionOnlyRoute",
routeTemplate: "{action}",
defaults: new { controller = "My" }
);
}
Here, we'll only look for an "action" in the URL and route everything to your "MyController" class.
After a few hours working on this and with a lot of help from the Route Debugger - Thanks Phil Haack! - I've found both the problem and the solution.
The problem: Route mapping matching is ordered, and RouteTable.Routes, from which the app's route mapping is initialized, contains quite a few of them. The request pattern I was looking for also matched some of these mappings ("authorize" was matched as a controller, for example).
The solution: add "my" route mappings before the default mappings.
Yeah, right...
Since most operations are not supported on HttpRouteCollection, the resulting code is a bit ugly, but it works:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
routesCopy = new RouteBase[RouteTable.Routes.Count];
RouteTable.Routes.CopyTo(routesCopy, 0);
config.Routes.Clear();
config.Routes.MapHttpRoute(name: "AuthorizeWebApiRoute", routeTemplate: "authorize", defaults: new { controller = "My", action = "authorize" });
config.Routes.MapHttpRoute(name: "ReleaseWebApiRoute", routeTemplate: "release", defaults: new { controller = "My", action = "release" });
foreach (var route in routesCopy)
{
RouteTable.Routes.Add(route);
}
}
}
As long as "my" route mappings don't match the default mappings (I made them specific enough not to), I'm all good - I think...
Related
I work on my web api project.
I have two get action methods in controller.
Here the controller:
namespace Playground.Web.Controllers.API
{
[RoutePrefix("api/DamageEvent/{actionType}")]
public class DamageEventController : ApiController
{
#region API methods
[HttpGet]
public async Task<IHttpActionResult> GetDamageEvent(int damageEventId = 0)
{
//some logic
}
[HttpGet]
[Route("{ddd:int}")]
public async Task<IHttpActionResult> GetDamageEvent2(int ddd = 0)
{
//some logic
}
#endregion
}
}
Here WebApiConfig defenition:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.JsonFormatter.SerializerSettings.DateFormatString = "dd/MM/yyyy";
}
}
Here the example of URL in fiddler compose to trigger web api action:
http://localhost/playground/api/DamageEvent/GetDamageEvent2/?ddd=22
I expect that for the URL above the GetDamageEvent2 web api action will be fired. But instead GetDamageEvent action method is fired.
Why GetDamageEvent2 not fired? Any idea what do I am missing?
==============================Update================================
After I red answer from Nkosi
I made some changes to my code, I added to class WebApiConfig new route:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
And here the changes in action type:
namespace Playground.Web.Controllers.API
{
[RoutePrefix("api/DamageEvent")]
public class DamageEventController : ApiController
{
#region API methods
[HttpGet]
[Route("GetDamageEvent/{damageEventId}")]
public async Task<IHttpActionResult> GetDamageEvent(int damageEventId = 0)
{
//some logic
}
[HttpGet]
[Route("GetDamageEvent2/{ddd}")]
public async Task<IHttpActionResult> GetDamageEvent2(int ddd = 0)
{
//some logic
}
#endregion
}
}
After I make the changes above the I tryed to fire the both actions and it worked.
But the problem now is when I try to call another actions in another controllers, For example:
http://localhost/playground/api/Contracts/1
I get 404 error.
So I guess the error occures because of the new route template.
So my question how can I fix the error above and to take the new route template into consideration only when the URI try to access to DamageEventController?
You are mixing attribute routing and convention based routing.
Nothing matches your RoutePrefix because there are no actions in the controller that has both a {actionType} and {ddd} templates.
But your stated URL...
api/DamageEvent/GetDamageEvent2/?ddd=22
...matches the DefaultApi convention based route for GetDamageEvent in the route table because it does not have a [RouteAttribute] and it defaults back the convention where...
api/{controller=DamageEvent}/{id=GetDamageEvent2/?ddd=22}
Take a look at Routing in ASP.NET Web API to understand the convention based routing.
and also Attribute Routing in ASP.NET Web API 2
Each entry in the routing table contains a route template. The default
route template for Web API is "api/{controller}/{id}". In this
template, "api" is a literal path segment, and {controller} and {id}
are placeholder variables.
When the Web API framework receives an HTTP request, it tries to match
the URI against one of the route templates in the routing table. If no
route matches, the client receives a 404 error. For example, the
following URIs match the default route:
/api/DamageEvent
/api/DamageEvent/1
/api/DamageEvent/GetDamageEvent2/?ddd=22
Once a matching route is found, Web API selects the controller and the
action:
To find the controller, Web API adds "Controller" to the value of the {controller} variable.
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 "GetDamageEvent". This
convention applies only to GET, POST, PUT, and DELETE methods. You can
enable other HTTP methods by using attributes on your controller.
We’ll see an example of that later.
Other placeholder variables in the route template, such as {id}, are mapped to action parameters.
To get your stated route to work you need to update your route templates. Either the attribute route or add a new convention route to the route table
I am working on Web Api. By default, it uses the api/{controller}/{id} as a url. I am able to have it to route api/device/{controller}/{id} but this will affect to all Web API route to that path.
But I only want certain controller to be in api/device/{controller}/{id} and the rest will go to another path.
I saw something like RoutePrefix but it doesn't seem to work...
[RoutePrefix (api/data/abc)] where abc is the controller name.
Add the custom route mapping in WebApiConfig.cs file before default route map:
By adding this before, any requests which matches the custom route will be executed, else the other one.
config.Routes.MapHttpRoute(
name: "CustomRoute",
routeTemplate: "api/device/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
If you want to customize the routing with a per controller approach, then you have to use Attribute Routing instead of the standard convention-based routing.
Decorate your controllers with RoutePrefix attribute, and your actions with Route attribute in this way:
[RoutePrefix("api/device/mydevice")]
public class MyDeviceController : ApiController {
[Route("{id}")]
[HttpGet]
public IHttpActionResult Get(int id) {
//DoWork
//...
}
}
And remember to enable attribute routing on the HttpConfiguration object:
config.MapHttpAttributeRoutes();
You may also remove MapHttpRoute method calls if you do not want to allow access to your actions in the standard convention-based way.
More on attribute routing on the official documentation.
I am trying to create a WebAPI controller with multiple Get commands using the ActionName method. I successfully did this on another project, but have been having problems with this latest project and cannot see to understand why my knockout view model ajax call cannot find the specific URI.
WebApiConfig.cs:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Controller:
// GET api/lot
[ActionName("Default")]
public IEnumerable<DataObject> Get()
{
//...
}
// GET api/lot/Specific/5
[ActionName("Specific")]
public IEnumerable<DataObject> Get(int? data)
{
//...
}
// GET api/lot/5
public string Get(int id)
{
return "value";
}
My default action for GET works perfect but the Specific action continues to have this error when I attempt to call it from the view-model:
"Failed to load resource: the server responded with a status of 404 (Not Found)"
I added [HttpGet] next to the [ActionName("Specific")] and had the following error:
"GET http://localhost:57492/api/lot/Specific/1 404 (Not Found)"
I've tried several different things such as removing the int? data argument, but then when I attempt to build the project it tells me an existing function with same arguments already exists, even with different action names.
Ultimately I would like to have multiple Get(parameter) actions to call for interacting with my view-model.
I was able to solve this by adding the Route property with HttpGet and renaming all of my functions to unique names like GetAll(), GetSpecific(ind id), etc..
// GET api/lot
[HttpGet]
[Route("api/lot/GetAll")]
public IEnumerable<DataObject> GetAll()
{...}
// GET api/lot/GetSpecific/{id}
[HttpGet]
[Route("api/lot/GetSpecific/{id}")]
public IEnumerable<DataObject> GetSpecific(string id)
{
Adding a new route in config.Routes as below. Will this help? I didn't test this.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/specific/{data}",
defaults: new {
action = "Specific",
data = RouteParameter.Optional }
);
I have two controllers.
one with classic REST functions.
public class ValuesController : ApiController
{
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
public string Get(int id)
{
return "value";
}
}
and one with custom function names with HttpAttributes
public class Values2Controller : ApiController
{
[HttpGet]
public IEnumerable<string> GetAll()
{
return new string[] { "value2_1", "value2_2" };
}
[HttpGet]
public string GetById(int id)
{
return "value " + id;
}
}
and I have my routes configured like this
config.Routes.MapHttpRoute(
name: "DefaultApi2",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
with this setting
http://localhost:xxxxx/api/Values2/1 does not get resolved
and if I put DefaultApi before DefaultApi2 then trying to access
http://localhost:xxxxx/api/Values2/GetAll
I get "The parameters dictionary contains a null entry for parameter 'id' "
The problem is that your route definitions are ambiguous. For example, for this URL:
http://localhost:xxxxx/api/Values2/1
the routing system can consider that the "1" segment is the action in the DefaultApi2 route, and the Id is null becasue you've defined it that way.
So, to break the ambiguity you need to use route constraints. This allows you to add rules to the different segments of your routes, so that they are not mismatched. Remember that the routes are eveluated in the order in which they are register.
So, for example, if you register first DefaultApi and then DefaultApi2, and you add a constraint which requires the Id for the first route to be numeric, both routes will work perfectly.
You could also add constraints on the controller name, or use a different prefix for each kind of controller for example api/ and api2/. The case is that the routes can be distinguished from each other.
Google "Web API route contraints", and you'll get info like this:
Creating a Route Constraint (C#)
The MSDN docs for RouteCollectionExtensions.MapHttpRoute Method (RouteCollection, String, String, Object, Object)are quite useless.
I've to provide two Web API controllers PublicController and PrivateController for our system. These should have the following routes:
/public/{controller}/{id}
and
/private/{controller}/{id}
On the firewall, all requests to /private are blocked and only available from inside the network. But by convention, both of my controllers are available for both routes, so I could request PrivateController (which should only be available under /private) with the url /public/PrivateController/1.
Is there a way to specify valid controllers for a route, so that the PrivateController is only available for the private route? Or are there some other practices to fullfill this requirement?
Thanks for your replies.
You can use the constraints parameter to provide restrictions on the controller name in the simplest case with a very simple regular expression:
config.Routes.MapHttpRoute(
name: "private",
routeTemplate: "api/private/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { controller = #"private" }
);
config.Routes.MapHttpRoute(
name: "public",
routeTemplate: "api/public/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { controller = #"public" }
);
So the now the "private" route only accept the controller which named Private and the "public" route will only accept the controller which named Public. If you have multiple public and private controllers you can easily extend the regex to match them.
If the regex is not enough for your needs you can create your custom route contaraint with implementing the IRouteConstraint interface. You can find an example implantation In this article.