We've implemented Swashbuckle on our Web Api project and i'm noticing that the SwaggerUI test harness displays the exact same information for any version specified in the address. More specifically it displays the swagger information for which ever VersionInfoBuilder comes first within the SwaggerConfig.
So for example if i navigate to "/preview/swagger/index" - the UI displayed is all v1 information not related to version 'preview'.
What am I doing wrong here?
public class SwaggerConfig
{
private SwaggerConfig() { }
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger("{apiVersion}/swagger", c =>
{
c.MultipleApiVersions(
(apiDesc, version) =>
{
var path = apiDesc.RelativePath.Split('/');
var pathVersion = path[0];
return CultureInfo.InvariantCulture.CompareInfo.IndexOf(pathVersion, version, CompareOptions.IgnoreCase) >= 0;
},
vc =>
{
vc.Version("v1", "Api - v1");
vc.Version("preview", "Api - Preview");
});
})
.EnableSwaggerUi("{apiVersion}/swagger/{*assetPath}", c =>
{
c.DisableValidator();
});
}
}
I don't think the configuration is wrong, it is just that your expectations are not correct.
You do not have to navigate to another Swagger UI (at preview/docs/index) but you have to point Swagger UI at another specification. Just enter http://yourserver:yourport/preview/swagger in the input box in the header and press Explore. Swagger UI will now load and display the preview specification.
Related
Feature Request
Today it is possible to version APIs and most things around that work perfectly fine with Swagger. What I am really missing here is the possibility to make it transparent for any Swagger UI users that an API version has been marked as deprecated.
API deprecation in aspnetcore is described here.
My expectation would be to have an icon or a tag which says "OBSOLETE" or "DEPRECATED" next to the API group name.
On a side note:
The Swashbuckle Swagger ASPNET.Core github project issue tracker advised to open feature requests on SO.
Edit:
The whole Controller is marked as deprecated using the ApiVersion attribute. If you mark the controller as [Obsolete] all the methods are grey and text is striked through. However this is not what I am looking for. I don't want mark my codebase [Obsolete]. I want to mark a specific API version as deprecated so people know they should switch to a newer version.
[ApiVersion("1", Deprecated = true)]
[Route("v{version:apiVersion}/[controller]")]
[Authorize("my.auth.policy")]
[ApiController]
public class MyApiController
{
// do stuff
}
My current workaround is this:
In my Startup I add the swaggerUI and do a custom formatting on the swagger endpoint dropdown display.
app.UseSwagger();
app.UseSwaggerUI(options =>
{
foreach (ApiVersionDescription apiVersionDescription in apiVersionDescriptionProvider.ApiVersionDescriptions.OrderByDescending(a => a.ApiVersion))
{
string isDeprecated = apiVersionDescription.IsDeprecated ? " (DEPRECATED)" : string.Empty;
options.SwaggerEndpoint($"{Configuration["PathBase"]}/swagger/{apiVersionDescription.GroupName}/swagger.json",
$"{apiVersionDescription.GroupName.ToUpperInvariant()}{isDeprecated}");
}
});
#helen
Definition
public class CustomHeaderFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
var apiDescription = context.ApiDescription;
if (apiDescription.IsDeprecated())
{
operation.Deprecated = true;
}
}
}
Use
builder.Services.AddSwaggerGen(g =>
{
g.OperationFilter<CustomHeaderFilter>();
}).AddSwaggerGenNewtonsoftSupport();
The interface must have the following
[ApiVersion("1.0", Deprecated = true)]
public class WeatherForecastController : ControllerBase
result
We use SwashBuckle to configure our AspNet Core (Service fabric) project to generate Swagger json and UI.
Then we use NSwagStudio to generate typescript and C# clients out of nswag.json template with swagger generated by Swashbuckle.
Recently there has been changes in latest version of NSwagStudio that generates clients with null checks added to response objects. We have existing Controllers returning null responses so the clients have stopped working.
Our sample controller endpoint declaration:
[HttpPost]
[ODataRoute]
[Produces("application/json")]
[ProducesResponseType(typeof(IActionResult), Status200OK)]
public Task PostXXX() => ...;
Our generated client (before)
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v13.6.2.0 (NJsonSchema v10.1.23.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
var status_ = ((int)response_.StatusCode).ToString();
if (status_ == "200")
{
var objectResponse_ = await ReadObjectResponseAsync<IActionResult>(response_, headers_).ConfigureAwait(false);
return objectResponse_.Object;
}
Our generated client (after)
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v13.7.0.0 (NJsonSchema v10.1.24.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
var status_ = (int)response_.StatusCode;
if (status_ == 200)
{
var objectResponse_ = await ReadObjectResponseAsync<Void>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new Exception("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
return objectResponse_.Object;
}
The question is how do we tell SwashBuckle to mark that this controller endpoint can return nullable response?
In other words, am looking for something similar to what's available in NSwag here in Swashbuckle world:
https://github.com/RicoSuter/NSwag/wiki/AspNetCoreOpenApiDocumentGenerator#response-nullability
More details in this github issue
https://github.com/RicoSuter/NSwag/issues/3011
Please let me know if you need more information.
I'm creating a Tool for Laravel Nova 2.0
In my Tool I want to send a list of stuff to the Vue component:
in the PHP src for my tool I have a function that generates the "meta", as suggested in the documentation here:
public function stuff() {
$stuff = [];
...
return $this->withMeta(['stuff' => $stuff]);
}
In my NovaServiceProvider.php I instantiate the tool and call the meta function. i.e.
public function tools()
{
return [
(new Tool())->stuff(),
];
}
However, nothing is passed to the Tool.vue component, (I have spent sometime inspecting it!) i.e.:
mounted() {
console.log(this.stuff); // undefined
},
Issue is discussed here: https://github.com/laravel/nova-issues/issues/761, however note that I am using a Tool and not a ResourceTool, or a Card.
Is this a bug with Tools, or something I'm doing wrong? Is there a workaround?
I haven't tried creating a custom tool yet, but for the custom field you can get the metadata using:
mounted() {
console.log(this.field.stuff);
},
if you're having troubles with similar stuff, i suggest printing the content of the class in the console in a json format, it makes it easier for you to troubleshoot the problem
mounted() {
console.log(JSON.stringify(this));
},
Although i don't think this is the problem can you try modifying your stuff() function to:
public function stuff()
{
$stuff = [];
$this->withMeta(['stuff' => $stuff]);
return $this;
}
Ripping api endpoints out of a .NET Core 1 web project to a .NET Core 2 api-only project. My experience with auth (both -orization and -entication) is minimal at best, mainly because most projects I've worked on have already been setup for auth and/or it's been an AD environment.
The API portion of the site uses a pre-shared token to be included in the header of every request. This token is the key to all auth, user identification, permissions, etc etc. The user info (ie. who are you and what can you do) is contained in a custom CurrentContext class.
The Core 1 project uses middleware (ContextMiddleware) to init the CurrentContext instance that is registered in DI as scoped. By the time the ContextMiddleware class is called, the custom auth handler has already been called, the necessary header token has already been examined, authentication checks have passed, and a principal has been created. Thus, the ContextMiddleware class, which heavily depends on the principal existing, can build the CurrentContext and the truckload of information needed to know who's calling.
The Core 2 project ends up running ContextMiddleware before the authentication handler, and I can't figure out how to force the order of those two to swap.
Relevant code snippets:
public class Startup {
public void ConfigureServices(IServiceCollection services) {
// ...
// https://geeklearning.io/how-to-migrate-your-authentication-middleware-to-asp-net-core-2-0/
services.AddAuthentication( options =>
{
options.DefaultScheme = UserTokenAuthenticationDefaults.AuthenticationScheme;
} ).AddUserTokenAuthentication(UserTokenAuthenticationDefaults.AuthenticationScheme,
UserTokenAuthenticationDefaults.AuthenticationScheme,
o => { } );
// ...
}
public void Configure(IApplicationBuilder app /*...*/) {
if (env.IsDevelopment()){
app.UseDeveloperExceptionPage();
} else {
app.UseExceptionHandler("/error/500");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMiddleware<ContextMiddleware>();
app.UseMvc();
}
}
If more code snippets are needed for further information, please let me know. How do I force my auth handler's HandleAuthenticateAsync() to run before ContextMiddleware is invoked?
I was dealing with this too and we found it best to support a "dynamic" authentication scheme we made up that then allows a selector to be called to make the authentication type decision.
Add a DynamicAuthenticationDefaults.AuthenticationScheme = "Dynamic"; constant somewhere in your codebase per ASP.NET Core standards, then in your startup class' ConfigureServices add the dynamic scheme:
.AddAuthentication( options =>
{
options.DefaultScheme = DynamicAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = DynamicAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = DynamicAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = DynamicAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = DynamicAuthenticationDefaults.AuthenticationScheme;
} )
.AddYourCustomAuthentication( YourCustomAuthenticationDefaults.AuthenticationScheme, YourCustomAuthenticationDefaults.AuthenticationScheme, options => { } )
.AddPolicyScheme( DynamicAuthenticationDefaults.AuthenticationScheme, DynamicAuthenticationDefaults.AuthenticationScheme, options =>
{
options.ForwardDefaultSelector = DynamicAuthenticationSchemaSelector.Evaluate;
} );
then in a custom DynamicAuthenticationSchemaSelector class implement that evaluation method:
public static class DynamicAuthenticationSchemaSelector
{
public static string Evaluate( HttpContext context )
{
string result;
var authHeader = context.Request.Headers["Authorization"].FirstOrDefault();
if( !string.IsNullOrEmpty( authHeader ) && authHeader.StartsWith( YourCustomAuthenticationDefaults.AuthenticationScheme ) )
{
result = YourCustomAuthenticationDefaults.AuthenticationScheme;
}
else
{
result = IdentityConstants.ApplicationScheme;
}
return result;
}
}
You'll get the proper authentication middleware handling.
You don't need to call this "dynamic" either and it's just for following best practices / patterns -- any string will suffice.
Using Swashbuckle in conjuntion with c.MultipleApiVersions((apiDesc, version) =>... the result is our swagger file resides at eg: https://host/api/swagger/docs/{version}. I would like to actually have the swagger file at https://host/api/{version}/swagger. Is it possible the I can set this up in my SwaggerConfig .EnableSwagger()?
This would allow for the following Urls :
http://host/api/v1/swagger/
http://host/api/v2/swagger/
Appreciate the help.
To do that way, you can update the swaggerconfig file as shown below:
.EnableSwagger("{apiVersion}/swagger", c =>
{
c.MultipleApiVersions(
(vc) =>
{
vc.Version("v2", "Swashbuckle Dummy API V2");
vc.Version("v1", "Swashbuckle Dummy API V1");
});
});
Just for further references in asp.net core 2.2 it will lokk like this
app.UseSwagger(options =>
{
options.RouteTemplate = "docs/{documentName}/docs.json";
});
app.UseSwaggerUI(options =>
{
//Build a swagger endpoint for each discovered API version
foreach (var description in provider.ApiVersionDescriptions.OrderByDescending(x=>x.ApiVersion).AsList())
{
options.SwaggerEndpoint($"/docs/{description.GroupName}/docs.json", description.GroupName);
}
options.RoutePrefix = "docs";
}
);
in which provider is IApiVersionDescriptionProvider injected by DI