I'm trying to move from Swagger/Swatchbuckle to NSwag but im running into issues trying to access swagger ui.
Since im using Service Fabric, all my apis are behind a reverse proxy.
Accessing the direct url:
http://localhost:60841/8f0077aa-6088-469d-9465-449acce86123/131654410884013502/swagger/
works, but accessing the reverse url:
http://localhost:19081/MyApp/MyService/swagger/
does not. It gets redirected to the direct url
http://localhost:19081/c94a3b54-f097-4cbe-bc82-d1e70a403134/131655768288075951/swagger/index.html?url=/c94a3b54-f097-4cbe-bc82-d1e70a403134/131655768288075951/swagger/v1/swagger.json
and reports a 404.
Accessing the reverse url did work when I was using swatchbuckle but then I also had this configuration:
app.UseSwagger(c =>
{
c.PreSerializeFilters.Add((swaggerDoc, httpReq) => swaggerDoc.BasePath = "/MyApp/MyService");
});
Does NSwag have a option to set PreSerializeFilters?
Is there another option that I can use to get the ui to work behind a reverse proxy?
I think the property you are looking for is PostProcess
I tried PostProcess but I could not get it to work :(
I think my problem has to do with the 302 redirect and a missconfigured endpoint.
When I was using swatchbuckle I manually added the endpoints like this:
app.UseSwaggerUI(options =>
{
var provider = app.ApplicationServices.GetRequiredService<IApiVersionDescriptionProvider>();
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerEndpoint($"{applicationBaseUrl}/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
});
Is there something similar in NSwag ?
You can configure the routes in the settings object
Is the problem, that you dont know the routes at configuration time? Can't you get the actual route via environment variable?
The problem I鈥檓 having at the moment is that the swagger gui is not showing up at all. All it gives me is a 404. I think Service Fabrics reverse proxy is messing things up but I鈥檓 gonna enable route tracing tomorrow to see what鈥檚 actually going on. I have a feeling I might need to create a new middleware to handle Service Fabrics X-forward header etc but I know more once I traced the routing. If I can box the specific configuration in a middleware, do you want a PR or shall I just type the solution here?
Btw, thanks for your quick replies and a awsome produkt :)
After much digging I found my initial problem:
If I entered the full url http://localhost:19081/MyApp/MyService/swagger/index.html it worked and I could see the Gui.
The problem seems to be theese lines of code https://github.com/RSuter/NSwag/blob/1eebd6a978d8a02cf861cffe189d4c7b4954350a/src/NSwag.AspNetCore/Middlewares/RedirectMiddleware.cs#L35-L36
When Service Fabric reverse proxy is used, context.Request.PathBase will contain the dynamicly temporary path for the micro service ie
/8f0077aa-6088-469d-9465-449acce86123/131654410884013502
So the page gets redirected to the wrong url.
A possible solution to this might be to change this line
https://github.com/RSuter/NSwag/blob/1eebd6a978d8a02cf861cffe189d4c7b4954350a/src/NSwag.AspNetCore/Middlewares/RedirectMiddleware.cs#L35
to also check it is coming from a proxy, like this:
if (context.Request.PathBase.HasValue && string.IsNullOrEmpty(context.Request.Headers["X-Forwarded-For"]))
but I can only verify if it works for Service Fabrics reverse proxy.
Another solution would be to write a custom middleware that changes the PathBase.
My second problem is that I can not find a way to add multiple Endpoint to Swagger, one for each of my API Versions
MS has a example here: https://github.com/Microsoft/aspnet-api-versioning/blob/master/samples/aspnetcore/SwaggerSample/Startup.cs where they use swashbuckle in combination with Api explorer to dynamicly add them at startup.
Is this possible in NSwag ?
What exactly is this doing? Generate one route per version and instruct Swagger UI to aggregate these in a single UI?
Yeah, and in the gui I can select the version i want to use and it shows me only thoose APIs.
I think this is not supported out of the box, there are three options in the middlewares:
I think versions are already supported but you'd need to call UseSwagger for each version with different routes. And I think there is no setting to specify multiple routes in the UI so that they are shown in a single UI.
The middleware should really respect Request.PathBase.
When hosting an aspnet core application behind a reverse proxy at path, this property is used for other middelware, and by all url-generation.
The middleware should also respect this property.
At the moment, the Authorizations through SwaggerUI appends the wrong callback string to the authorization-endpoint, loosing the PathBase component.
@Eneuman adding multiple endpoints to the ui is now supported (SwaggerRoutes), just call UseSwagger/UseSwaggerWithApiExplorer() multiple times and register the routes in SwaggerRoutes
Can someone create a PR to respect the BasePath in the middlewares?
Workaround that at least works for AspNetCore:
Edit: this is not working: see my next comment
The middleware is actually using a "PathBase", but a custom one on the SwaggerUISettingsBase called MiddlewareBasePath.
To make swaggerUI redirect properly when hosted at a PathBase other than /, set this property to whatever the app uses, and prepend the same path to settings.SwaggerRoute and swaggerUiRoute.
Given that the app is hosted at /api add this line at the start of your Startup.Configure-method:
app.UsePathBase("/api");
and to the Swagger middleware config:
app.UseSwaggerUi3WithApiExplorer(settings => {
// settings.SwaggerRoute = "/api/swagger/v1/swagger.json"; edit: does not work
settings.SwaggerUiRoute= "/api/swagger";
settings.MiddlewareBasePath = "/api";
});
Maybe we can use UsePathBase() to "initialize" MiddlewareBasePath? Or does this break other things? Or is this a breaking change?
Most other middleware is silenty adhering to the PathBase, and not trying to do much path calculations.
But for SwaggerUI and AspNetCoreToSwaggerMiddleware, the PathBase usage is strange.
My previous attempted fix actually turned out unexpected.
The generated Swagger-spec has the wrong document.BasePath. The spec-generation is removing the basepath by substringing away the MiddlwareBasePath, meaning the fix to SwaggerUi breaks the spec-generation.
Overriding the document.BasePath mends this error.
A full workaround is:
public void Configure(IApplicationBuilder app, IHostingEnvironment env){
var basePath = "/api";
app.UsePathBase(basePath); //This should go first.
//Other interesting middleware
app.UseSwaggerUi3WithApiExplorer(settings => {
settings.SwaggerRoute = string.Concat(basePath, "/swagger/v1/swagger.json");
settings.SwaggerUiRoute = string.Concat(basePath, "/swagger");
settings.MiddlewareBasePath = basePath;
settings.PostProcess = document => document.BasePath = basePath;
});
}
Better late than never. I managed to have it working that way:
From serviceContext of type StatelessServiceContext, I provide my service fabric settings via a custom ReverseProxyOptions,
services.AddSingleton(
new ReverseProxyOptions()
{
AppPath = serviceContext.ServiceName.AbsolutePath,
AppTitle = serviceContext.CodePackageActivationContext.CodePackageName,
AppVersion = serviceContext.CodePackageActivationContext.CodePackageVersion
});
services.AddSingleton(serviceContext);
Then I configure NSwag with the request headers and reverse proxy options:
```
var reverseProxyOptions = app.ApplicationServices.GetService
app.Use((context, next) =>
{
if (context.Request.Headers.TryGetValue("X-Forwarded-Host", out var c))
{
context.Request.PathBase = $"{reverseProxyOptions?.AppPath}";
}
return next();
});
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthentication();
app.UseCors(options => options.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
app.UseSwagger(
settings =>
{
settings.Path = "/swagger/"+ reverseProxyOptions.AppVersion + "/swagger.json";
settings.PostProcess = (document, request) =>
{
document.Schemes = new[] { request.Headers["X-Forwarded-Proto"].FirstOrDefault() == "http" ? SwaggerSchema.Http : SwaggerSchema.Https };
document.Info.Title = reverseProxyOptions.AppTitle;
document.Info.Version = reverseProxyOptions.AppVersion;
document.BasePath = reverseProxyOptions.AppPath;
document.Host = request.Headers["X-Forwarded-Host"].FirstOrDefault();
};
});
app.UseSwaggerUi3(options =>
{
options.DocumentPath = "/swagger/" + reverseProxyOptions.AppVersion + "/swagger.json";
});