Service Fabric Web API Versioning issue - .net-core

I'm working on a service fabric project with multiple stateless services. When i try to add versioning as in the code below
[Authorize]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class SessionController : Controller
{
...
}
it's not working when calling the service later using Postman or using some client winforms app i made just to call this service. And when i say it's not working i mean it's not looking for a specific version i placed in the controller.
e.g.
I'm calling http://localhost:1234/api/v1.0/session/set-session and as you can see in my controller i only have version 2.0. Now my API gets hit this way or another no matter what version number i put in.
I added code to the Startup.cs
services.AddApiVersioning(options => {
options.DefaultApiVersion = new ApiVersion(2, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = new HeaderApiVersionReader("x-api-version");
});
Specific API call looks like this:
[HttpPost]
[Route("set-session")]
public async Task<IActionResult> SetSession([FromBody] SessionModel model)
{ ... }
Can anyone tell me what am i missing or maybe api versioning is not supported in service fabric at all?
Thanks.

Does your solution work locally? Based on what I see, I would suspect - no. This should have nothing to do with Service Fabric at all.
Issue 1
I see that your base class inherits from Controller, which is allowed, but is usually ControllerBase. No concern there, just FYI. The crux of the problem is likely that your controller has not applied the [ApiController] attribute. API Versioning defines IApiControllerSpecification and IApiControllerFilter, which is used to filter which controllers should be considered an API. This is important for developers building applications that have the UI and API parts mixed. A controller is a controller in ASP.NET Core and it was difficult to distinguish these two in the early days. There is now a built-in IApiControllerSpecification that considers any controller with [ApiController] applied to be an API. This can be changed, replaced, or completely disabled using ApiVersioningOptions.UseApiBehavior = false.
If your library/application is only APIs, you can decorate all controllers at once using:
[assembly: ApiController]
Since your controller is not currently being considered an API, all requests matching the route are being directed there. The value 1.0 is being considered an arbitrary string rather than an API version. This is why it matches at all instead of HTTP 400. I suspect you must only have one API and it is defined as 2.0; otherwise, I would expect an AmbiguousActionException.
Issue 2
Your example shows that you are trying to version by URL segment, but you've configured the options to only consider the header x-api-version. This option should be configured with one of the following:
URL Segment (only)
options.ApiVersionReader = new UrlSegmentApiVersionReader();
URL Segment and Header
// registration order is irrelevant
options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("x-api-version"));
Default (Query String and URL Segment)
// NOTE: this is the configuration
// options.ApiVersionReader = ApiVersionReader.Combine(
// new QueryStringApiVersionReader(),
// new UrlSegmentApiVersionReader());
Side Note
As defined, using the URL segment and header versioning methodologies don't make sense. You have a single route which requires an API version. A client will always have to include the API version in every request so there is no point to also supporting a header.
If you define 2 routes, then it makes sense:
[Route("api/[controller]")] // match by header
[Route("api/v{version:apiVersion}/[controller]")] // match by url segment
Versioning by URL segment, while common, is the least RESTful. It violates the Uniform Interface constraint. This issue demonstrates yet another problem with that approach. Query string, header, media type, or any combination thereof will all work with the single route template of: [Route("api/[controller]")]
Observation 1
You have configured options.AssumeDefaultVersionWhenUnspecified = true. This will have no effect when versioning by URL segment. It is impossible to provide a default value of route parameter in the middle of a template. The same would be true for api/value/{id}/subvalues if {id} is not specified.
This option will have an effect if you:
Add a second route template that doesn't have the API version parameter
You update your versioning strategy to not use a URL segment
It should be noted that is a highly abused feature. It is meant to grandfather in existing services that didn't previously have explicit versioning because adding it will break existing clients. You should be cognizant of that if that isn't your use case.

Related

Verisioning a Webapi .net-core using with VersionByNamespaceConvention and not being forced to use RouteAttribute on Controllers

The idea is to have a versioned API by URL like this:
/api/v1/weatherforecast
/api/v2/weatherforecast
/api/v3/weatherforecast
My namespace is the following:
On the Startup I have this code to register the versioning:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddApiVersioning(
options =>
{
// reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
options.ReportApiVersions = true;
// automatically applies an api version based on the name of the defining controller's namespace
options.Conventions.Add(new VersionByNamespaceConvention());
});
}
So I wanted to remove the "[Route("api/v{version:apiVersion}/[controller]")]" Attribute in every Controller is this possible?
I was trying to map the controllers using something like this:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "api",
pattern: "api/v{version:apiVersion}/{controller}/{id?}"
);
});
}
But when I run the code it gives the following error:
Action 'ApiVersioning.Api.V3.Controllers.WeatherForecastController.Get (ApiVersioning)' does not have an attribute route. Action methods on controllers annotated with ApiControllerAttribute must be attribute routed.'
So can I add an implicit routing convention for all Controllers that removes the need of the RouteAttribute on top of every Controller?
The error message you are seeing is a restriction of the built-in API Explorer. It only supports Attribute Routing. If you don't need that for documentation, then it shouldn't be a problem.
Unlike the ASP.NET of old, there is no distinction between a UI Controller and an API Controller. However, there are certain behaviors developers expect between them. API Versioning requires that all controllers are versioned, even if implicitly via the default API version (and otherwise undecorated). This behavior was undesirable for those mixing UI and APIs together. Once the [ApiController] attribute was introduced, there was finally a way to reliably identify whether a controller is for an API.
To test this, you can simply change ApiVersioningOptions.UseApiBehavior = false. This will revert back to the old behavior. Bear in mind that this will result in all controllers being versioned.
The alternate approach is to implement IApiControllerSpecification or IApiControllerFilter. Typically you only need IApiControllerSpecification as the filter is an aggregation of all specifications. Register your specification(s) or filter with the DI container. The implementation of a specification decides whether a controller is for APIs given a ControllerModel. There is a built-in specification that looks for [ApiController]. You might create one that looks for ControllerBase as the base class or use some other mechanism such as prefix in the route template or containing namespace. The choice is up to you. The specification will allow you to use your own conventions without having to use [ApiController].
You could also use a base class for all controllers with the attribution you want in a single place. It's not exactly the same as using a standard route convention, but it will achieve a similar result. You may need that approach if you expect to support API documentation.

ASP.Net Web API - Extract header in common place

I am working on SaaS application where I have implemented ASP.Net Web API as a service layer. In my Web API, when any request generated, it will have one header value "x-companyid" which is company specific and identify request comes from which company (Tenant).
I require that companyID in all my ApiControllers in Web API project. Of course I can get header value in every ApiController by using "Request.Headers.GetValues("x-companyid") but it will be repeated in all ApiControllers.
I have tried to create "BaseApiController" and inherit all my ApiControllers from "BaseApiController" but it's not allowing me to override ActionExecuting so that I can extract header at common place.
Can anyone suggest how can I extract "x-companyid" header commonly in my project so that I don't have to repeat code in all ApiController?
Your Best friend are action filters.
as they intercept each api call for specific controllers.
All u had to do is to decorate the controler name with something u made like
[Tenant]
public foo MyController()
{}
this approach gives you controll over what controllers you wish to add this extract to happen becouse maybe youll need some lookup apis and stuff that dont need to be for a specific Tenant
here is a very Helpful link :
https://www.c-sharpcorner.com/article/filters-in-Asp-Net-mvc-5-0-part-twelve/
Update
since there is a only one company for each instance
i would recommend adding a singleton for the current tenant u can access that anyway
https://en.wikipedia.org/wiki/Singleton_pattern

Spring-Security-OAuth2 - how to add fields to access token request?

I have a Spring Boot application, that is using Spring Security with OAuth 2.0. Currently, it is operating against an Authentication Server based on Spring Example code. However, running our own Auth Server has always been a short-term target to facilitate development, not a long-term goal. We have been using the authorization_code grant type and would like to continue using that, irrespective of the Auth Server implementation.
I am attempting to make changes to use OAuth 2.0 Endpoints in Azure Active Directory, to behave as our Authentication Server. So far, I have a successful call to the /authorize endpoint. But the call to get the /token fails with an invalid request error. I can see the requests going out.
It appears that parameters that Azure states as mandatory are not being populated in the POST request. Looking at the Azure doco, it expects the client_id to be defined in the body of the message posted to the endpoint, and that is not added, by default, by Spring.
Can anyone point me in the right direction for how I can add fields to the Form Map that is used when constructing the Access Token request? I can see where the AccessTokenRequest object is being setup in OAuth2ClientConfiguration....
#Bean
#Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
protected AccessTokenRequest accessTokenRequest(#Value("#{request.parameterMap}")
Map<String, String[]> parameters, #Value("#{request.getAttribute('currentUri')}")
String currentUri) {
DefaultAccessTokenRequest request = new DefaultAccessTokenRequest(parameters);
request.setCurrentUri(currentUri);
return request;
}
Should I be trying to define the map in a request.parameterMap spring property? If so, I'm not too sure how that works.
Or should I be using one of the interfaces defined in the AuthorizationServerConfigurerAdapter class?
I have the information to include when sending the AccessTokenRequest, I just don't know the best way to configure Spring to include it? Thanks for any help.
Actually, I found this out. I needed to change the client authentication scheme. Simply adding the following to my application properties added the client_id to the form....
security.oauth2.client.clientAuthenticationScheme=form
If you're using yaml, then yaml-ize it. Thank you Spring!

Html.BeginForm in WebApi - routing

I have a Web API project as part of my solution (also containing an MVC4 project) and within the Api project I am trying to post a form to the Values controller Post method (from a view also within the Api project).
Using Html.BeginForm() or Html.BeginForm("Post", "Values") posts to /Values/Post but I need it to go to /api/Values/Post
Any idea which overload or settings I need to post to the correct location?
I can hit all the action methods fine from fiddler (e.g. localhost/api/values).
You would need to use BeginRouteForm as link generation to Web API routes always depends on the route name. Also make sure to supply the route value called httproute as below.
#using (Html.BeginRouteForm("DefaultApi", new { controller="Values", httproute="true" }))
The API controller uses a different route to the default. It's supposed to be consumed from JS (AJAX) rather than a real form post so there's no obvious support for it in HtmlHelpers. Try:
Html.BeginForm("values", "api")
This would trick it into thinking "values" is the action and "api" is the controller. "Post" is inferred from the http method.

Html.RouteLink to a Web API Route - possible?

My site is largely a suite of web services exposed via the Asp.Net Web API. There are also pages, designed to support the webservices (testing etc), written in Razor (and implicitly Asp.Net MVC 4).
For the XML versions of the webservices I have a schema-export action (uses the XsdDataContractExporter) which is picked up by my standard API route (although note - I've flipped the precedence of the Web API and Pages):
//page routes
routes.MapRoute(
"Default", // Route name
"pages/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home",
action = "Index",
id = UrlParameter.Optional
} // Parameter defaults
);
//an additional route for my Schema controller action
routes.MapHttpRoute("XSD", "schema.xsd",
new { controller = "schema" });
//API Catch-all Route
routes.MapHttpRoute("APIMain", "{controller}/{id}",
new { id = RouteParameter.Optional });
Now on a razor page I want to emit a link to the 'friendly' schema URL ~/Schema.xsd. Anticipating issues with route discovery I immediately went for hitting the route directly by name:
#Html.RouteLink("Schema", "XSD");
However this just emits a link equivalent to ~/.
I've tried some other combinations of route values - but it appears MVC's HtmlHelper and UrlHelper simply don't want to pick up Web API routes.
I'm sure if I cracked open the source of Asp.Net MVC 4 I'd find the reason - but I'm hoping somebody already knows, and since I can't find another SO about such cross-linking I figured it'd be a good addition to the SO library.
I should add that browsing to ~/Schema and ~/Schema.xsd do correctly display the XML schema produced by the API action.
Update
Post-RC a method was added to MVC's UrlHelper, HttpRouteUrl, which does exactly the same thing I suggest here in this answer. This is my discussion thread over on CodePlex where I was told this. So there is no need for you to use the magic string mention here in generating links to Web API routes.
Original answer
I've managed to get it to work - although it might not by the time MVC 4 is RTMd (disclaimer disclaimer!)
I changed my Html.RouteLink call as follows:
#Html.RouteLink("XML request schema", "XSD", new { httproute = true })
I didn't originally intend to answer my own question straight away - but having done some research I found an answer.
First I verified that the HtmlHelper's route collection is the same as the RouteTable.Routes collection (i.e. contained all routes).
Following the call-chain through, I remembered having trawled through the current Web API and page MVC 4 source code from CodePlex, that HttpRoutes (in System.Web.Http.Routing) need a 'hidden' route value to be added otherwise they will never match. Here's the source code from lines 21-25 of HttpRoute class (correct as of 8th June 2012 source):
/// <summary>
/// Key used to signify that a route URL generation request should include HTTP routes (e.g. Web API).
/// If this key is not specified then no HTTP routes will match.
/// </summary>
internal const string HttpRouteKey = "httproute";
A bit of further analysis of the code showed that it expects this route value to be a boolean.
Clearly, this is something that can be turned into extension methods - perhaps Html.HttpRouteLink (and Html.HttpActionLink) - with extra extensions on UrlHelper for hiding the magic string for the route data value.

Resources