-
Notifications
You must be signed in to change notification settings - Fork 712
error with bynamespacewebapi sample #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This is actually the correct behavior. I just realized that the comments in the code are not correct. Despite the route configuration indicating that the accountId route parameter is optional, it isn't. You may have noticed that each version of the service has only a single GET action: public IHttpActionResult Get( string accountId ) => Ok( /* omitted */ ); This action will match I will update the sample so that the comments and route map match the expected behavior. Thanks for reporting this. I hope that helps. |
FYI, the sample has been updated. |
Awesome, thx Chris. I try to make this namespace versioning to work with attribute routing. |
Great. To version by namespace and use attribute routing in Web API, you just need to register the route constraint in the configuration. var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof( ApiVersionRouteConstraint )
}
};
configuration.MapHttpAttributeRoutes( constraintResolver ); After that, things will be pretty standard in your controllers. It should look something like: namespace Services.V1.Controllers
{
[ApiVersion( "1.0" ), Route( "api/v{version:apiVersion}/my" )]
public class MyController : ApiController { /* omitted */ }
}
namespace Services.V2.Controllers
{
[ApiVersion( "2.0" ), Route( "api/v{version:apiVersion}/my" )]
public class MyController : ApiController { /* omitted */ }
}
namespace Services.V3.Controllers
{
[ApiVersion( "3.0" ), Route( "api/v{version:apiVersion}/my" )]
public class MyController : ApiController { /* omitted */ }
} |
hi Chris, I try to make it work with Swagger. adding the routeprefix Route( "api/v{version:apiVersion}/my" ), the version become a mandatory input parameter for all method. that's odd. also even with the Route( "api/v1/my" ) config to make both controller show up on swagger, when invoking the api through the swagger ui will got an error saying { |
try to extend the ApiVersionControllerSelector. turns out everything else is internal. I guess there little use of making virtual methods in these selectors |
I've provided some pretty detailed responses to supporting API documentation in #46 and #54. Succinctly, I will say that there is no out-of-the-box support for API documentation - yet. It's something that I've thought about and there are APIs to query the models, but that has not formalized into any included functionality. I will open up some issues to track these requests. If the existing comments for issues #46 and #54 don't get you want you need, I'm happy to answer more questions. It may be somewhat involved on your side, but it's definitely possible to get all of the API version information for each controller without subclassing anything. |
The ApiVersionControllerSelector does have some virtual methods, but as you mention, they aren't terribly useful. This is largely the same surface area as the original DefaultControllerSelector.cs implementation. I'm not opposed to opening things up, but - honestly - it's a deep, dark path that you don't really want to go down. Controller and action selection is at the heart of what the Web API library for API versioning provides. If you can articulate what you'd like to get out of subclassing the ApiVersionControllerSelector, I can probably explain how to achieve your goals without using inheritance. |
to fix the issue on hand, I need to override the SelectController method in ApiVersionControllerSelector class. since Route( "api/v{version:apiVersion}/my" ) does not work for the same controller name in two different version folder. I need to modify if all logic fail to get the correct controller name. I haven't really look into the implementation detail yet. so i am not sure if these are the right place for modification but so far it's working. |
with all that, it works perfectly with swashbuckle. i even customize the swagger UI for version selection but I don't want to maintain the customized code base of your lib here. just want to add couple of classes to inherit from the existing class to get what I want. |
I've added #59 and #60 to track these issues and enable others, like yourself, to post feedback on. I'm not sure I completely follow about the same route for two different controllers in different namespaces. If your controller type names are the same, then that will make things easier for documentation. The design of Web API doesn't support the notion of multiple controller types (e.g. descriptors) for a given controller name. I was able to make this work, however, by creating a custom HttpControllerDescriptorGroup. This is a custom HttpControllerDescriptor that aggregates all of the descriptors together. This is not something easily disseminated or inferred from the implementation, but it's the only thing I could come up without breaking the existing Web API interfaces. This means you can flatten and traverse the descriptors like so: var controllerMapping = configuration.Services.GetHttpControllerSelector().GetControllerMapping();
var controllerDescriptorGroup = controllerMapping["My"];
var controllerDescriptors = controllerDescriptorGroup as IEnumerable<HttpControllerDescriptor> ??
new []{ controllerDescriptorGroup };
foreach ( var controllerDescriptor in controllerDescriptors )
{
// TODO: enumerate each implementation of "My" service
} This is just the tip of the iceberg. Issues #46 and #54 go into much more detail about the approaches and how to get at the API version models for controllers and their actions. Using the ApiVersionControllerSelector should not be necessary at all for documentation purposes. |
it' s not the same route for the two different controllers in different namespace. it's different route marked by routeprefix "v1/my" and "v2/my". notice it's not using the apiVersion route constraint as you suggested in the routeprefix because when using that, the two controllers wont' show up in swagger and somehow the version become a mandatory input parameter in all methods within the controller. |
Yes ... the apiVersion route constraint will be mandatory. To resolve the route, you can use the approach above. You can aggregate the ApiVersionModel for a given logical service by name. Using the aggregated models, you can fill-in the route template parameter. Expanding from above: var serviceVersionModel = controllerDescriptors.Select( cd => cd.GetApiVersionModel() ).Aggregate();
var routeValues = new RouteValueDictionary() { /* you other route template parameter values */ };
foreach ( var apiVersion in serviceVersionModel.ImplementedApiVersions )
{
routeValues["version"] = apiVersion.ToString();
IHttpRoute route = ResolveRoute( configuration, routeValues );
// TODO: route paired with descriptor and API version
} You can try to match routes to controllers based off of the route prefix, but that will be magic string matching. You're better off using the UrlHelper or other components to provide the API version for the route you want to resolve as a route template value. Just a suggestion. |
so after all I need to modify your code base instead of writing couple of classes. do you think open up the internal classes an option? |
I think the version route constraint is a bit intrusive. don't you think? it added overhead for the caller. instead just putting the version info in the routeprefix as is e.g. RoutePrefix["v1/blah/"], it's just a matter of getting the version info from the routeprefix. because the routeprefix is kind of mandate the url to have the version info there. so the caller will follow the convention and state their intend in the url. |
Using a route constraint removes the magic string interpretation in URL. The infrastructure cannot determine where in the URL the version segment is. Consider the difference between In terms of filling in the route template, it's no different than filling in the controller or id parameters. You can choose to fill in the parameters from well-known data or you can discover it dynamically from the meta-model. [ApiVersion( "2.0" )]
[RoutePrefix( "api/v{version:apiVersion}/blah" )]
public class BlahController : ApiController
{
[Route( Name = "Blah-V2" )]
public IHttpActionResult Get() => Ok();
[Route( "{id:int}", Name = "BlahById-V2" )]
public IHttpActionResult Get( int id ) => Ok();
} Figure 1: Basic controller with versioned URL segment To generate a URL, you would just need: UrlHelper url = null; // get or create UrlHelper
var link = url.Link( "BlahById-V2", new { version = "1", id = 42 } ); Figure 2: Build a URL Admittedly, I'm not a fan of API versioning by URL path segment. URLs should be stable over time IMHO. Documentation and client usage is easier by query string or even header because the URL and route never changes. Since a header or query parameter are not part of the URL path, they also don't identify a resource by definition. One alternative that might make things easier for you using this approach would be to use convention-based routing instead of attribute routing. I really like attribute routing, but convention-based routing would probably make the documentation process much easier since there would be only a single route with a single name. |
I see your point but things always change as everything else in this universe :) I try attribute routing and made it work nicely but the requirement is to keep the same controller name between versions. hence i found this nice lib you made. excellent job btw . Sent from my Samsung device -------- Original message -------- Using a route constraint removes the magic string interpretation in URL. The infrastructure cannot determine where in the URL the version segment is. Consider the difference between v1/blah and api/v1/blah. The location of the version segment is not guaranteed. The API versioning support never assumes or tries to infer the API version from the URL using Regex or some other string matching. In terms of filling in the route template, it's no different than filling in the controller or id parameters. You can choose to fill in the parameters from well-known data or you can discover it dynamically from the meta-model. [ApiVersion( "2.0" )]
} Figure 1: Basic controller with versioned URL segment To generate a URL, you would just need: UrlHelper url = null; // get or create UrlHelper Figure 2: Build a URL Admittedly, I'm not a fan of API versioning by URL path segment. URLs should be stable over time IMHO. Documentation and client usage is easier by query string or even header because the URL and route never changes. Since a header or query parameter are not part of the URL path, they also don't identify a resource by definition. One alternative that might make things easier for you using this approach would be to use convention-based routing instead of attribute routing. I really like attribute routing, but convention-based routing would probably make the documentation process much easier since there would be only a single route with a single name. You are receiving this because you modified the open/close state. |
run the sample. point the url to
http://localhost:1676/v1/agreements
got this error:
UnsupportedApiVersion
The HTTP resource that matches the request URI 'http://localhost:1676/v1/agreements' does not support the API version '1'. No route providing a controller name with API version '1' was found to match request URI 'http://localhost:1676/v1/agreements'.what am I mssing?
thanks
The text was updated successfully, but these errors were encountered: