I need to сlarify.
I have .net mvc app and I use Microsoft/aspnet-api-versioning (for ASP.NET Core).
And I have 2 controllers:
[ApiVersion("1.0")]
[Route("[controller]")]
public class OneController : Controller
{
[HttpGet]
public string Get()
{
return "Hello. I'm OneController";
}
}
and
[ApiVersion("1.1")]
[Route("[controller]")]
public class TwoController : Controller
{
[HttpGet]
public string Get()
{
return "Hello. I'm TwoController";
}
}
TwoController I added after release API with OneController.
And now if I try to use "http://localhost:59719/One?api-version=1.1" i see error:
The HTTP resource that matches the request URI 'http://localhost:59719/test?api-version=1.1' does not support the API version '1.1'.
Should I use different versions for different controllers or there is way to use one (latest) version for any request?
I understand I can add [ApiVersion("1.1")] to ALL controllers, but if I have 20 controllers...
Thanks for help.
You can define a default api version using the ApiVersioningOptions class and use this default version if none is specified:
`services.AddApiVersioning(o =>
{
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(1 , 0);
});`
Besides, you should take a look at this excellent post on API versioning in ASP.NET from Scott Hanselman:
https://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspx
In your example above, you have two different controllers with two different API versions. There is no direct relationship between these two. If you're trying to apply API versions in a centralized manner, you can achieve this using the Conventions API. You can even author and apply your own conventions via the IControllerConvention interface.
// same result as the attributes above
options.Conventions.Controller<OneController>().HasVersion(1,0);
options.Conventions.Controller<TwoController>().HasVersion(1,1);
// can also be achieved using only controller types
options.Conventions.Controller(typeof(OneController)).HasVersion(1,0);
options.Conventions.Controller(typeof(TwoController)).HasVersion(1,1);
// register your own convention that applies API versions to all controllers
options.Conventions.Add(new MyCustomApiVersionConvention());
You can also use an approach similar to what #arnaudauroux suggested, which would be:
services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.ApiVersionSelector = new CurrentImplementationApiVersionSelector(options);
});
Now any request without an API version will select the current (or highest) API version available. Beware that this could break clients.
Related
While trying to add routing to additional endpoints within my project built with IdentityServer4, I ran into the following issue:
Error: Scope IdentityServerApi not found in store.
I followed IdentityServer4's documentation for adding more API Endpoints exactly, so I wasn't sure why I was getting this error.
It turns out that in v4 IdentityServer is moving away from ApiResources and toward ApiScopes. For anyone just learning about IdentityServer4, it can be a little confusing navigating all of these changes, so hopefully this clarification will be helpful to someone else as well.
Solution: as opposed to what it says in the documentation included in my question, register your "LocalApi" as an ApiScope not an ApiResource. Following in an example of my setup for a local API:
Your config file:
// Add to your ApiScopes (not ApiResources)
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope(IdentityServerConstants.LocalApi.ScopeName)
};
// Then add to your client's allowed scopes:
// ... extraneous code omitted ...
AllowedScopes = new string[]
{
IdentityServerConstants.LocalApi.ScopeName
}
Your startup/program.cs:
// Register IdentityServer, then:
builder.Services.AddLocalApiAuthentication();
Your "LocalApi" controller class:
[ApiController]
[Authorize(LocalApi.PolicyName)]
[Route("api/[controller]/[action]")]
public class YourController : ControllerBase
{
// ...
}
In Shopware 6, I want to call a backend (/admin) API controller from a backend / admin page using JavaScript. What is the correct way to use a relative path, probably with a built-in getter function?
Fetching /api/v1 only works if the shop is on /, but not when it is in a sub-folder.
fetch('/api/v1/my-plugin/my-custom-action', ...)
The best practice would be to write your own JS service that handles communication with your api endpoint.
We have an abstract ApiService class, you can inherit from. You can take a look at the CalculatePriceApiService for an example in the platform.
For you an implementation might look like this:
class MyPluginApiService extends ApiService {
constructor(httpClient, loginService, apiEndpoint = 'my-plugin') {
super(httpClient, loginService, apiEndpoint);
this.name = 'myPluginService';
}
myCustomAction() {
return this.httpClient
.get('my-custom-action', {
headers: this.getBasicHeaders()
})
.then((response) => {
return ApiService.handleResponse(response);
});
}
}
Notice that your api service is preconfigured to talk to your my-plugin endpoint, in the first line of the constructor, which means in all the following request you make you can use the relative route path.
Keep also in mind that the abstract ApiService will take care of resolving the configuratuion used for the Requests. Especially this means the ApiService will use the right BaseDomain including subfolders and it will automatically use an apiVersion that is supported by your shopware version. This means the apiVersion the ApiService uses in the route will increase every time a new api version is available, that means you need to work with wildcards in your backend route annotations for the api version.
Lastly keep in mind you need to register that service. That is documented here.
For you this might look like this:
Shopware.Application.addServiceProvider('myPluginService', container => {
const initContainer = Shopware.Application.getContainer('init');
return new MyPluginApiService(initContainer.httpClient, Shopware.Service('loginService'));
});
If you are talking about custom action that you implemented, you need to define route (use annotation) and register controller in routes.xml in your Resources\config\routes.xml.
Please follow that documentation
https://docs.shopware.com/en/shopware-platform-dev-en/how-to/api-controller
I searched for my problem beforehand in various sources but the answers did not provide me with a solution.
I implemetend Url based web api versioning in a .net core 2.2 project with the way presented here. The version that I used for versioning is the latest Microsoft.AspNetCore.Mvc.Versioning 3.1.2.
I also tried to understand how it works from the following sources: source1, source2, source3, source4.
I am having a ValueController with a GET method in a folder called Initial and a Value2Controller in a folder called New. Both folders are subfolders of the 'Controllers' folder.
The structure is as follows:
The routing in ValueController is
[Route("api/v{version:apiVersion}/[controller]")]
and in Value2Controller is:
[Route("api/v{version:apiVersion}/value")]
I have also set options.EnableEndpointRouting = false; in the Startup.cs and I tried calling api/v1/value or api/v2/value. Both times I get the error: Multiple actions matched. It cannot differentiate between the two controllers actions.
I tried using services.AddApiVersioning(); with no options at all and remove AddVersionedApiExplorer. It does not work. The only thing that works is
putting
[Route("api/v{version:apiVersion}/[controller]")]
in both controllers and make the following api calls:
api/v1/value and api/v2/value2.
The configuration in my startup.cs is as follows:
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ApiVersionReader = new UrlSegmentApiVersionReader();
options.UseApiBehavior = true;
});
services.AddVersionedApiExplorer(
options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
});
What am I missing to call either api/v1/value or api/v2/value and go to the correct request?
After some more debugging, I finally figured out why it wasn't working, so I am posting the solution to anyone who will face a similar problem. The problem was within the Controller Inheritance.
I had created a CustomBaseController (which I had completely disregarded as problematic for some reason) with some methods for global exception handling, the inheritance goes as follows:
[ApiVersionNeutral]
[Route("api/[controller]")]
[ApiController]
CustomBaseController : Controller
and
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
ValuesController : CustomBaseController { // http method implementations}
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/values")]
[ApiController]
ValuesController : CustomBaseController { // updated http method implementations}
The versioning mechanism did not agree with [ApiVersionNeutral] attribute even though it made sense to me that the the base controller would not need to change at all. Moreover I only had the basic routing in the base controller.
Thus I got the error with "Multiple actions matched".
I also found out that the version 1 controller, can inherit the routing from the base controller and had no reason to have a routing there. For all subsequent controllers, the routing must be:
[Route("api/v{version:apiVersion}/values")].
The working solution along with the initial configuration posted above, is the following:
[Route("api/v{version:ApiVersion}/[controller]")]
[ApiController]
CustomBaseController: Controller {}
[ApiVersion("1.0")]
[ApiController]
ValuesController: CustomBaseController { //code }
[ApiVersion("2.0")]
[ApiController]
[Route("api/v{version:ApiVersion}/values")]
Values2Controller: CustomBaseController { //code }
[ApiVersion("3.0")]
[ApiController]
[Route("api/v{version:ApiVersion}/values")]
Values3Controller: CustomBaseController { //code }
Getting values from the following urls:
api/v1/values
api/v2/values
api/v3/values
Even though my issue was resolved, I still don't understand why [ApiVersionNeutral] would cause the routing to not be able to detect the versions of the other controllers correctly. Any explanation would be highly appreciated.
Thank you #Matt Stannett for your comments, they led me to the right direction.
I have a spring boot application , which have a spring MVC controller. I am trying to version my rest api using Accept header.
The following is how my Controller looks like
RestController
#RequestMapping(value = "/private/")
public class AppleController {
private final AppleService appleService;
public AppleController(AppleService appleService) {
this.appleService = appleService;
}
#GetMapping(value = "apples/{id}", produces = "application/json; v=1.0",
headers = "Accept=application/json; v=1.0")
public ResponseEntity getByappleId(#PathVariable("id") Long appleId) {
System.out.println("version1");
GetByappleIdResponse response = appleService.findByappleId(appleId);
return new ResponseEntity<>(response, HttpStatus.OK);
}
#GetMapping(value = "apples/{id}", produces = "application/json; v=2.0",
headers = "Accept=application/json; v=2.0")
public ResponseEntity getByappleId2(#PathVariable("id") Long appleId) {
System.out.println("version2");
GetByappleIdResponse response = appleService.findByappleId2(appleId);
return new ResponseEntity<>(response, HttpStatus.OK);
}
Irrespective of the version that I am passing in the Accept header when calling the API always "getByappleId" method is called, hence only version 1 response is returned.
Is there anything wrong in my controller ?
There are many options to implement versioning of REST API:
suggested in the comments approach for manually routing your request;
making version as a part of your Accept header value, f.e.:
(headers = "Accept=application/vnd.name.v1+json")
(headers = "Accept=application/vnd.name.v2+json")
making version as a part of your mapping:
#GetMapping("apples/v1/{id})"
#GetMapping("apples/v2/{id})
So you need to decide which way to go. Some useful links:
Versioning a REST API
Best practices for API versioning?
As described in this answer: https://stackoverflow.com/a/34427044/258813 (and mentioned in the comments) Spring does not support routing using the headers like that.
If you want to support routing via a version header, I would recommend a custom routing condition and annotation - certainly if you are building a large API, it will result in less code and a more elegant solution.
You would define some annotation like #ApiVersion(1) that you can add to any method that is also a request mapping and then add the custom routing condition and it will behave correctly.
I have described using custom routing conditions and annotations (based on subdomains - but that could easily be switched to check headers instead) here: http://automateddeveloper.blogspot.co.uk/2014/12/spring-mvc-custom-routing-conditions.html
I m looking for a example how to use netflix-feign from a NO-spring-boot app.
I have a existing SpringMVC (4.2) webapp. Now i build some "microservices" with Spring-boot + (eureka, feign) and i want to use those as backend serivces from the webapp.
thanks in advance
In the github repo you can see a few examples to use feign-client:
https://github.com/OpenFeign/feign
Basically, you would need to create an interface annotated with feign annotations and not with the Spring, one example would be (you can see much more in the github page):
public interface YourClient {
#RequestLine("POST /")
#Headers("Content-Type: application/json")
Response getSomething(#Param("id") String id);
}
Then to instantiate your feign client it's need to use its builder Feign. It's easy, and configurable:
YourClient yourClient = Feign.builder()
.decoder(new GsonDecoder()) // you could use Gson, Jackson,...
.target(YourClient .class, "https://your.url");
Then you can use it just invoking the method you want:
yourClient.getSomething("myId");