AttributeRouting with IHttpControllerSelector - Api Versioning - asp.net

I am trying to achieve api versioning using a CustomHttpControlSelector and AttributeRouting on asp.net webapi.
What i am trying to do is distinguish controller's versions by it's namespaces.
if a request is made to /api/v2/foo/bar
i want it to match
namespace Web.Controllers.Api.v2
{
[RoutePrefix("foo")]
public class LongerThanFooController : ApiController
{
[HttpGet]
[Route("bar")]
public string BarFunction()
{
return "foobar";
}
}
}
but as i see when i don't use full url on RoutePrefix (/api/v2/foo) attribute routing doesn't kick in and i get null when i call
request.GetRouteData().GetSubRoutes();
on my CustomHttpControlSelector. i don't want to Repeat /api/v2 on every controller.
if i decide to remove attributeRouting and use manual routes like
config.Routes.MapHttpRoute(
name: "DefaultVersionedApi",
routeTemplate: "api/v{version}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional, version = Config.LatestVersion }
);
i lose all flexibility of naming my controllers and functions.
is there a way to get out of this limbo?
note: for the CustomHttpControlSelector i modified code on http://aspnet.codeplex.com/SourceControl/changeset/view/dd207952fa86#Samples/WebApi/NamespaceControllerSelector/NamespaceHttpControllerSelector.cs

I realize this is bit of an old question now, but it can answered using the ASP.NET API Versioning package for ASP.NET Web API. In the latest 3.0 version, you can achieve your scenario by updating your configuration with:
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof( ApiVersionRouteConstraint )
}
};
configuration.AddApiVersioning(
options =>
{
options.Conventions.Add( new VersionByNamespaceConvention() );
options.AssumeDefaultVersionWhenUnspecified = true;
options.ApiVersionSelector = new CurrentImplementationApiVersionSelector( options );
} );
configuration.MapHttpAttributeRoutes( constraintResolver );
You should also remove your convention-based routes. Those are unnecessary if you are using attribute routing.
The setup for your controller simply changes to:
namespace Web.Controllers.Api.v2
{
[RoutePrefix("api")]
public class LongerThanFooController : ApiController
{
[HttpGet]
[Route("foo/bar")]
[Route("v{version:apiVersion}/foo/bar")]
public string BarFunction()
{
return "foobar";
}
}
}
The reason you need two route definitions is that you cannot have default values in the middle of the route template. Default values can only be used at the end. This also means that you need to allow no API version to be specified and indicate the way to determine which API version should be selected is to use the current implementation (e.g. latest). I'm personally not a fan of this approach because I think things should be predictable to clients, but this will achieve your desired result.

Related

ASP.NET ApiController inside a webform can't reach methods

I can't reach any methods from my ApiController in anyway, the routing does appear if i try to reach it by a browser but no methods are shown.
My Controller:
namespace AgroRiego.Controllers
{
public class datacontrol : ApiController
{
[HttpGet, Route("api/get")]
public string Get([FromUri]string user, string pass)
{
string check = SQL.Reader("SELECT * FROM users WHERE username='" + user + "' AND password='" + pass + "'");
if (String.IsNullOrWhiteSpace(check))
{
return "error en credenciales";
}
DataTable horarios = SQL.table_read("SELECT * FROM horario_riego");
string json = Utils.ConvertDataTabletoJSON(horarios);
return json;
}
[HttpPost, Route("api/post")]
public void Post([FromBody]string value)
{
string i = value;
}
}
}
my global asax:
namespace AgroRiego
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
}
and my webapiconfig:
namespace AgroRiego
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// ConfiguraciĆ³n y servicios de API web
// Rutas de API web
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
i have more webforms inside the project (originally it was just html pages with serverside code, but i need to add a couple methods to retrieve and send data, help much appreciated!
EDIT1: i managed to reach HTTP 200 changing the URL but i can't reach the methods anyway (in debug mode it does not stop on the breakpoints) how can i route correctly the Api (so it is not Login.aspx) and how do i fix the methods reaching?
EDIT2: i read in documentation that i need this line in global:
RouteConfig.RegisterRoutes(RouteTable.Routes);
but im not using MVC does that matter? i tried reaching the routes with a brand new MVC Web Api and it yields "No Response"
use a routerprefix with your controller. So you access the URL as
http://localhost/routerprefix/router
HttpClient class can be use to send and receive HTTP requests and responses. Since you are trying to consume a WebApi from a aspx page, better way is to create a HttpClient instance
Below is a very simple implementation. Please check this url for further information
MSDN sample
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("http://localhost:49342/api/get");
if (response.IsSuccessStatusCode)
{
product = await response.Content.ReadAsAsync();
}
By the look of your set up, it seems correct
you have got:
config.MapHttpAttributeRoutes(); - setup the attribute route
config.Routes.MapHttpRoute( - setup the default route
GlobalConfiguration.Configure(WebApiConfig.Register); - to register at startup
so it should work.
I think the problem you are having is the way you are calling it
WebAPI routing work a little different to MVC
for example:
In you get method, the route is set as below
[HttpGet, Route("api/get")]
so you should call it {host}/api/get using a GET http method
in the screen shot, you are calling using {host}/api/get/Get - which would not have work, because no route would match
Same for your POST method
So give it another try and you should be able to reach it
The URL to add in the rest testing tool is
http://localhost:49342/api/get
Method type is GET
If you are calling this web api from aspx page use the httpClient class.

Url.Action returning incorrect URL for webapi action with Route attrubute

I have a problem with the behaviour of Url.Action();
I have a webapi where all controllers require explicit route prefix attribute and all actions require a route attribute.
I register my routes in the WebApiConfig.cs
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof( ApiVersionRouteConstraint )
}
};
config.MapHttpAttributeRoutes(constraintResolver);
I have currently commented out the line below, but (because) it did not change the incorrect behaviour:
//config.Routes.MapHttpRoute(name: "DefaultApi",
//routeTemplate: "api/v{version:apiVersion}/{controller}/{action}/{id}", defaults: new {id = RouteParameter.Optional});
My controllers look as follows:
[RoutePrefix("api/v{version:apiVersion}/programs")]
public class ProgramsController : ApiController
{
[HttpGet, Route("{telemetryKey}/versions/latest")]
public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
{
// serious business logic
}
}
I expect that '#Url.Action("GetLatestVersionInfo", "Programs", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })'
should return /api/v1/programs/43808405-afca-4abb-a92a-519489d62290/versions/latest
however, I get /Programs/GetLatestVersionInfo?telemetryKey=43808405-afca-4abb-a92a-519489d62290 instead. So, my routeprefix and route attributes are ignored.
Swagger correctly discovers my routes and I can validate that requests to the expected routes work OK - it's only the Url.Action() that is confused.
What can be wrong...?
Well, it seems there were a few things wrong.
Wrong helper:
I should be using the Url.HttpRouteUrl for generating API links from a razor view (Url.Link is for generating link from within API controllers)
Conflict with aspnet-api-versioning library
For some reason (perhaps a bug?) the prefix that I have on the controller (apiVersion variable) breaks the URL helper mechanism.
For now, I have ditched the aspnet-api-versioning library, but created an issue on their github repo, in case its a bug.
Since I really hate the idea of creating and maintaing magic strings, so I took the following approach - each controller has a public static class which contains const values for the route names:
[RoutePrefix("api/v1/developers")]
public class DevelopersController : ApiController
{
[HttpGet, Route("{developerId}/programs", Name = Routes.GetPrograms)]
public async Task<IEnumerable<Program>> GetPrograms(Guid developerId){}
public static class Routes
{
public const string GetPrograms = nameof(DevelopersController) +"."+ nameof(DevelopersController.GetPrograms);
}
}
Now that can be used from a razor controller in a simple and relatively safe manner:
#Url.HttpRouteUrl(DevelopersController.Routes.GetPrograms, new { developerId = /* uniquest of guids */})
A bit better than magic strings. I've also added a bunch of unit tests for controllers where I validate that each route is unique and proper and that the routes class only contains routes for the action it contains.
Try the following:
Name your route:
[HttpGet, Route("{telemetryKey}/versions/latest", Name="LatestVersionInfoRoute")]
public async Task<LatestVersionResponse> GetLatestVersionInfo(Guid telemetryKey)
{
// serious business logic
}
Use Url.Link method:
#Url.Link("LatestVersionInfoRoute", new { telemetryKey = Guid.Parse("43808405-afca-4abb-a92a-519489d62290") })

Attribute routes missing from routing table when using multiple namespaces

I am using namespace versioning in my web api (4.0) project. I'm using Attribute routing to define custom routes for our actions. This works just fine when a controller only exists in one namespace/version.
But when a controller exists in both namespaces/versions, the attribute route is ignored completely. I am using the solution here to look at what routes are in the routing table. And it only contains one route, for my "single" controller. If I delete/comment out/change the name of the controller in V2, then suddenly the "double" controller's route appears in the routing table as well.
Is there any reason that an attribute route would be ignored because the same class/controller name exists in 2 different namespaces? Here is my code, broken down to the simplest failing example:
namespace API.v1
{
public class SingleController : ApiController
{
[HttpGet]
[Route("api/{version}/Single")]
public string Test()
{
return "Response";
}
}
}
This class/route works just fine. http://localhost:57560/api/v1/Single returns "Response".
namespace API.v1
{
public class DoubleController : ApiController
{
[HttpGet]
[Route("api/{version}/Double")]
public string Test()
{
return "Response";
}
}
}
namespace API.v2
{
public class DoubleController : ApiController
{
[HttpGet]
[Route("api/{version}/Double")]
public string Test()
{
return "Response";
}
}
}
These 2 classes fail; the attribute route is missing from the routing table, and http://localhost:57560/api/v1/Double returns 404, as well as http://localhost:57560/api/v2/Double. If I just change the name of the v2.Double class, then it works just fine.
In this example, I have no default routes set up at all; only the Attribute routing. When using default routes, the versioning and routing work just fine:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{version}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
This route works perfectly across multiple versions. But I need it to work with attribute routing.
I have been able to solve the issue using the solution here: http://abhinawblog.blogspot.com/2014/12/web-api-versioning-using.html. The problem appears to be related to the way that attribute routing differs from using the default routes table. It is required that the routes have unique names, and that the version numbers in the routes have custom constraints put on them.
In order to make the namespace versioning possible, you need to override the default behavior of routing. Check this MSDN Link for more details
Also check this sample code to see how it's done

asp.net WEB API: Routing - Action before Controller. Possible workaround?

I am developping a WEB API using asp.net and my goal is to be able to call this type of url's:
/html/countries/...
/json/countries/...
Countries is a controller, and depending on the parameter before it returns different result.
What i did and it doesn't seems to work:
routes.MapRoute(
name: "Default",
url: "api/{action}/{controller}",
defaults: new
{
}
);
CountriesController:
[ActionName("html")]
public string get()
{
//...
}
[ActionName("json")]
public void getType()
{
//...
}
Any sugestions?
EDIT:
I have like 7 controllers.
And there are some possible urls:
/html/{controller}/x/y
/json/{controller}/x/y/order/h
/html/{controller}/x/z/order/y/j
/json/{controller}/x/z/order/y/j
Allow me by start saying that if "html" or json action means "format" those should not be part of your controller they are media types and needs to be configured differently
Web Api v1 defines resources globally in the global.asax on application_start event. Assuming you are using Visual Studio 2013 and based Microsoft default template your method may look like this:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
WebApi routing configuration occurs here WebApiConfig.Register while your MVC configuration occurs here RouteConfig.RegisterRoutes
Your WebApi routing configuration should look like this
public static class WebApiConfig{
public static void Register(HttpConfiguration config){
config.Routes.MapHttpRoute(
name: "htmltApi",
routeTemplate: "html/{action}/{controller}",
);
config.Routes.MapHttpRoute(
name: "jsonApi",
routeTemplate: "json/{action}/{controller}",
);
...
Another important detail is that WebApi v2 introduced something called Route Attributes those can be used along with your Controller class and can facilitate the routing configuration.
For example:
public class BookController : ApiController{
//where author is a letter(a-Z) with a minimum of 5 character and 10 max.
[Route("html/{id}/{newAuthor:alpha:length(5,10)}")]
public Book Get(int id, string newAuthor){
return new Book() { Title = "SQL Server 2012 id= " + id, Author = "Adrian & " + newAuthor };
}
[Route("json/{id}/{newAuthor:alpha:length(5,10)}/{title}")]
public Book Get(int id, string newAuthor, string title){
return new Book() { Title = "SQL Server 2012 id= " + id, Author = "Adrian & " + newAuthor };
}
...
Thanks to the Dalorzo answer i did find the problem:
The problem occurred because my application was created the following way:
which resulted in creation of both files, RouteConfig.cs (MVC) and WebApiConfig.cs (WEB API):
What is WRONG is that the code in the question is from RouteConfig.cs
After putting the code
config.Routes.MapHttpRoute(
name: "Default",
routeTemplate: "api/{action}/{controller}",
defaults: new
{
action = "html"
}
);
in WebApiConfig.cs, it worked proper way.

Asp.net Web API: No action was found on the controller

So this has really been causing me a headache.
Route:
config.routes.MapHttpRoute(
name: 'ControllerAction' ,
routeTemplate: "api/{controller}/{action}"
);
Controller:
public class LookupsController : ApiControllerBase
{
[ActionName("all")]
public Lookups GetLookups()
{
var lookups = new Lookups
{
Customers = GetCustomers().ToList(),
//add more
};
return lookups;
}
}
but whenver I try to hit the uri: /api/lookups/all
I get a 404 error saying:
"No action was found on the controller 'Lookups' that matches the name 'all'."
Any help would be appreciated
EDIT:
So I figured it out finally.
it was because of the wrong depency.
VS2012 autoresolved the action to system.web.mvc.actionnameattribute while what I needed was system.web.http.actionnameattribute.
weird problem, anyways, I hope this helps someone else.
Jakob Li,
EDIT:
You are renaming the controller but not specifying the method. WebApi resolves the method from the prefix GET, PUT, DELETE, POST. So the method attribute must be placed at the signature of the action:
Try this, works well for me:
public class LookupsController : ApiControllerBase
{
[HttpGet()]
[ActionName("All")]
public Lookups GetLookups()
{
var lookups = new Lookups
{
Customers = GetCustomers().ToList(),
//add more
};
return lookups;
}
}
routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Take a look here for more information about routing:
Routing in ASP.NET Web API
Hopes its help you!

Resources