I am trying to get versioning to work via the request header in .NET 5 using Microsoft.AspNetCore.OData v8.0.1 and versioning to work with SwaggerUI.
Pre-v8, you used to be able to use
services.AddODataApiExplorer(...);
which would enable DependencyInjection for the Startup's Configure method:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider) {
...
app.UseSwagger();
app.UseSwaggerUI(options => {
// build a swagger endpoint for each discovered API version
foreach (var description in provider.ApiVersionDescriptions) {
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
options.ShowExtensions();
});
...
}
OData8 appears to not handle this out of the box and the best documentation I can find on versioning only handles URL Segment and hints at Query String versioning. I'd prefer to save characters in the URL if I can which is why I want to go with Request Header versioning.
Any guidance is appreciated.
So, instead of using
services.AddODataApiExplorer(...);
you can use
services.AddApiVersioning(...);
It won't (currently) get picked up properly by the SwaggerUI but it will generate the client code properly and respond to versioning by routing to the target versioned controller.
I was able to find the most clear and concise documentation on versioning in the article, 'REST API versioning with ASP.NET Core'
//WHEN VERSIONING BY: query string, header, or media type
endpoints.MapVersionedODataRoute( "odata", "api", modelBuilder );
//WHEN VERSIONING BY: url segment
endpoints.MapVersionedODataRoute( "odata-bypath", "api/v{version:apiVersion}", modelBuilder );
Related
Problem: Given a .NET Core web API endpoint that looks like this:
[HttpPost]
public async Task<ActionResult> MyEndpoint () { }
When a request is made, I want to be able to know in all cases, in the middleware layer, that the endpoint is defined as [HttpPost].
Entire issue at hand:
I want to add global error logging to every endpoint. So I decided to add some custom middleware.
public void Configure (IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseMyCustomLoggingMiddleware(); // my custom middleware
app.UseCors();
app.UseAuthentication();
app.UseEndpoints();
}
In my custom middleware, in the success case I can easily check the endpoint verb/attribute using the Endpoint class.
public async Task InvokeAsync (HttpContext context)
{
Endpoint endpoint = context.GetEndpoint();
// endpoint.Metadata has the information i need! it knows it's a POST!
}
However, the error case is what I'm struggling with. MyEndpoint above is defined as a [POST] endpoint, so if someone attempts to make a [GET] request against it, I want to be able to log that error specifically. The same for other mismatched HTTP commands.
But, because we Configure our app to app.UseRouting(), in the error case, when we use the incorrect HTTP command, the call to Endpoint endpoint = context.GetEndpoint() stops returning the actual Endpoint where we can access the metadata/verb (likely because it can't find it because of the mismatched HTTP verb\command), and instead returns an empty Endpoint object set to {405 HTTP Method Not Supported}.
If I register my custom middleware before the call to app.UseRouting() in an attempt to get the default routing to not automatically return 405, then the Endpoint endpoint = context.GetEndpoint() call simply returns null, which is also bad.
How can I get the fact that the target endpoint is defined as a [HttpPost] in all cases?
I would like to avoid reflection if at all possible because I am logging high noise events and don't want to be hampering our performance.
I would probably create two middlewares.
Error middleware
Logging middleware
Invalid requests will not pass the routing and you can hardly know the endpoint definition before it.
public void Configure (IApplicationBuilder app, IWebHostEnvironment env)
{
// here you can handle the errors raised in the routing
app.UseMyCustomErrorMiddleware();
app.UseRouting();
// log valid requests here
app.UseMyCustomLoggingMiddleware();
app.UseCors();
app.UseAuthentication();
app.UseEndpoints();
}
I am using MVC ASP.NET Core 3.1 and React.NET and I am getting this issue.
When I render my component, the component renders, but the props are always null. It is almost as if the Html.React render method isn't properly passing the values over, please help!
I'm only going to add relevent code to the react (my startup.cs has more settings)
Startup.cs
public void ConfigureServices(IServiceCollection services) {
services.AddJsEngineSwitcher(options => options.DefaultEngineName = ChakraCoreJsEngine.EngineName).AddChakraCore();
services.AddReact();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseReact(config =>
{
// If you want to use server-side rendering of React components,
// add all the necessary JavaScript files here. This includes
// your components as well as all of their dependencies.
// See http://reactjs.net/ for more information. Example:
config
.AddScript("~/scripts/react_common/login.jsx");
config.SetLoadBabel(true);
});
}
index.cshtml (or any view, just trying to use this HTML extension helper)
#Html.React("Login", new
{
Test = "Test"
}, serverOnly: true)
login.jsx
class Login extends React.Component {
render() {
return <div>{this.props.Test}</div>
}
No matter what I do, it will never display "Test" for example. I need to know why it isn't passing the values into the props. I am starting to lose my mind over this problem, it worked just fine before I started migrating to .NET Core.
More details (Nuget Packages)
React.Asp.Net(5.1.2)
React.AspNet.Middleware(5.1.2)
Please help.
The default JSON serializer contract resolver is set to automatically convert it into camelCase (React). You have to over-ride this behavior if you want it to maintain the supplied case - in the Configure method in startup.cs:
app.UseReact(...
app.UseStaticFiles();
//Ensure to place this after the UseRact statement above
ReactSiteConfiguration.Configuration.JsonSerializerSettings.ContractResolver = new DefaultContractResolver();
Hopefully this helps someone else from going crazy
I'd like to prepare my .NET Core Web API project so that multiple versions of the API can be managed and documented, according to the REST services standards.
I'm using .NET Core 2.1 with NSwag (v11.18.2). I also installed the Microsoft.AspNetCore.Mvc.Versioning NuGet package.
I already searched with Google for some configuration examples, but the only useful link I found is this.
I'm now able to get Swagger pages for both API versions but with some problems:
Please note that none of the last config settings (Title, Description, etc.) takes effect on any of the 2 routes. It only works if I add them on each of the individual configuration. So I'd also like to know if it possible to avoid that, since the general configuration of the API can be version indipendent (title, description and so on...).
Since the issue with NSwag and Microsoft API Versioning package discussed in the above link, was opened 2-3 months (and NSwag versions too) ago, I'd like to know if it is now truly fixed and in this case, which is the right configuration to set.
Although the version is explicit in the configuration of the controllers, it is still required as a mandatory input parameter of the controller methods and of course I don't want that! See image:
So, my actual configuration, by following that example, is looking like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ReportApiVersions = true;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSwaggerWithApiExplorer(config =>
{
config.GeneratorSettings.OperationProcessors.TryGet<ApiVersionProcessor>().IncludedVersions = new[] { "1.0" };
config.SwaggerRoute = "v1.0.json";
});
app.UseSwaggerWithApiExplorer(config =>
{
config.GeneratorSettings.OperationProcessors.TryGet<ApiVersionProcessor>().IncludedVersions = new[] { "2.0" };
config.SwaggerRoute = "v2.0.json";
});
app.UseSwaggerUi3(typeof(Startup).GetTypeInfo().Assembly, config =>
{
config.SwaggerRoutes.Add(new SwaggerUi3Route("v1.0", "/v1.0.json"));
config.SwaggerRoutes.Add(new SwaggerUi3Route("v2.0", "/v2.0.json"));
config.GeneratorSettings.Title = "My API";
config.GeneratorSettings.Description = "API functionalities.";
config.GeneratorSettings.DefaultUrlTemplate = "{v:apiVersion}/{controller}/{action}/{id?}";
config.GeneratorSettings.DefaultPropertyNameHandling = PropertyNameHandling.CamelCase
});
}
And these are my actual controllers:
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]/[action]")]
[SwaggerTag("Test1", Description = "Core operations on machines (v1.0).")]
public class MachinesController : Controller
{
[HttpGet("{id}")]
[ProducesResponseType((int)HttpStatusCode.OK)]
public async Task<ActionResult<Machine>> Get(int id)
{
return await ...
}
}
[ApiController]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]/[action]")]
[SwaggerTag("Test2", Description = "Core operations on machines (v2.0).")]
public class MachinesController : Controller
{
[HttpGet("{id}")]
[ProducesResponseType((int)HttpStatusCode.OK)]
public async Task<ActionResult<Machine>> Get(int id)
{
return await ...
}
}
They are ignored in the middleware because they are inferred from the settings or do not apply for api explorer (template). However title and description should work...
Please create an issue with the specific issue and a repro, also check out the existing tests in the repo
Fixed with v11.18.3
I believe starting in NSwag 12.0.0, there is significantly improved support for the API Explorer. It's important that the complementary API Explorer package for API versioning is also referenced so that the proper information is provided to NSwag.
The Swagger sample application provided by API Versioning uses Swashbuckle, but the setup will be very similar to NSwag. You can use the IApiVersionDescriptionProvider service to enumerate all of the API versions defined in your application. That should significantly simplify your NSwag configuration.
You're versioning by URL segment; therefore, to address Problem 3 you simply need to configure the API Explorer a la:
services.AddVersionedApiExplorer( options => options.SubstituteApiVersionInUrl = true );
This will replace the {version} route parameter in the route template with the corresponding API version value and remove the API version parameter from the API description.
We are using Web API 2 on our project with Swagger. My problem is that when Microsoft.AspNet.WebApi.Versioning is applied as following:
the Swagger UI is ignoring the fact that now I have version in my API which needs to be provided.
I looked at several examples but none seem to address this issue in a satisfying manner.
How do I force Swagger to let me add the API version or just add the version number automatically to the URL?
Swagger configuration so far:
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "MoovShack.ServerApi");
// If your API has multiple versions, use "MultipleApiVersions" instead of "SingleApiVersion".
// In this case, you must provide a lambda that tells Swashbuckle which actions should be
// included in the docs for a given API version. Like "SingleApiVersion", each call to "Version"
// returns an "Info" builder so you can provide additional metadata per API version.
//
//c.MultipleApiVersions(
// (apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion),
// (vc) =>
// {
// vc.Version("v2", "Swashbuckle Dummy API V2");
// vc.Version("v1", "Swashbuckle Dummy API V1");
// });
c.OperationFilter<MoovShackTokenHeaderParameter>();
})
.EnableSwaggerUi(c =>
{
// If your API has multiple versions and you've applied the MultipleApiVersions setting
// as described above, you can also enable a select box in the swagger-ui, that displays
// a discovery URL for each version. This provides a convenient way for users to browse documentation
// for different API versions.
//
//c.EnableDiscoveryUrlSelector();
});
You can see that so far MultipleApiVersions are disabled - from one good reason as it doesn't produce any results. Especially since I am not sure what "ResolveVersionSupportByRouteConstraint" should do.
I also read that "EnableDiscoveryUrlSelector" has some kind of impact but I am also not sure if that applies to my case. When I enabled it, nothing happened.
We use it like this in our project and swagger recognizes it and it looks fine
[ApiVersion( "1.0" )]
[Route("api/v{version:apiVersion}/[controller]")]
public class SomeControlelr: Controller{
[HttpGet("", Name = "Someaction"), MapToApiVersion("1.0")]
public async Task<IActionResult> SomeAction(string someParameter)
Using .Net Core v1.1 to create an OData service, it works fine connecting from a browser, though fails when connecting from Excel 2016, as a basic OData Feed. The Exception is
'InvalidOperationException'
No media types found in 'Microsoft.AspNetCore.OData.Formatter.ODataOutputFormatter.SupportedMediaTypes'.
Add at least one media type to the list of supported media types.
Both Excel and the browser connect to ‘http://localhost:52315/odata’ with Method = ‘GET’
HeaderAccept from the browser is:
"text/html, application/xhtml+xml, image/jxr, */*"
HeaderAccept from excel is: (NOTE: I added new lines after ';' for readability)
"application/json;
odata.metadata=minimal;
q=1.0,application/json;
odata=minimalmetadata;
q=0.9,application/atomsvc+xml;
q=0.8,application/atom+xml;
q=0.8,application/xml;
q=0.7,text/plain;
q=0.7"
Any insights into how a media type can be added, and which should be added is appreciated. Can't seem to find samples or previous questions dealing with this.
This is apparently a known issue (cf https://github.com/OData/WebApi/issues/597). It seems unlikely that it's gonna be solved in the vNext branch, but fortunately, I found a workaround.
In your Startup.cs, just after registering Odata, add this snippet:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddOData();
services.AddMvcCore(options =>
{
// loop on each OData formatter to find the one without a supported media type
foreach (var outputFormatter in options.OutputFormatters.OfType<ODataOutputFormatter>().Where(_ => _.SupportedMediaTypes.Count == 0))
{
// to comply with the media type specifications, I'm using the prs prefix, for personal usage
outputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/prs.dummy-odata"));
}
});
}
This will find all the output formatters that don't declare a supported media type, and add a dummy one.
References:
vNext code that adds an improper formatter
same code in the master branch