If you've been working with ASP.NET, chances are, Swashbuckle might be familiar to you. It's basically a package library that generates Swagger definitions/specs which includes a UI for documenting and testing your APIs without using Postman or other similar tools. With a simple configurations in your Startup.cs class, your application should provide a self documenting page that entails what your API is all about. Deploying your API straight to a public facing server let's you browse through the Swagger API document without any configurations as you can access them just like when running your application locally. However, deploying your API behind a proxy or gateway is a bit different as the route may change and you have to do some configurations to be able to expose your Swagger documentation.

Assuming that you've made all the changes in Azure API Management (APIM) to be able to expose your Swagger API definitions and UI by adding a bunch of APIM operations with routes poiting to Swagger UI CSS, JavaScripts and JSON file that are needed to render the Swagger UI.

The Scenario

You have an API that sits behind a reverse proxy. For example in Azure Front Door /API Management, etc, you name it. As an example, let's say the public facing URL of your API looks like this:

https://{DOMAIN}.com/{BASEPATH}/v1/foo

Depending on how you configured the Swagger UI route in APIM, let say the route /swagger/index.html will point to your Swagger UI. The public URL would look something like this:

https://{DOMAIN}.com/{BASEPATH}/swagger/index.html

The Swagger UI renders as what you would expect but you've noticed that the "Try it out" functionality doesn't work anymore and get that nasty 404 Not Found error or worst, the undocumented TypeError: Failed to fetch error.

The main reason for this is that the Server Url that the Swagger UI uses to pull your API metada is invalid. The Server Url from your swagger definition JSON file might include the internal port to it and ultimately strip the BASEPATH segment which ends up your Server Url to look something like this:

https://{DOMAIN}.com:80

That means that, when you test out the  GET /v1/foo endpoint, the Swagger UI will construct the URL to this: https://{DOMAIN}.com:80/v1/foo - which is invalid because what you are expecting is something like this: GET https://{DOMAIN}.com/{BASEPATH}/v1/foo.

The Interim Solution

I literally spent a whole day trying to find a clean solution without much manual configuration on our end, but couldn't get an elegant way to get the base path from swagger configuration. I searched the net, pulling my hair and tried various implementations and approaches to no avail. For the time being, here's what I did to fix it:

app.UseSwagger(options =>
{
    options.PreSerializeFilters.Add((swagger, httpReq) =>
    {
         if (httpReq.Headers.ContainsKey("X-Forwarded-Host"))
         {
            //The httpReq.PathBase and httpReq.Headers["X-Forwarded-Prefix"] is what we need to get the base path.
            //For some reason, they are returning as null/blank. Perhaps this has something to do with how the proxy is configured which I don't have control.
            //For the time being, the base path is manually set here that corresponds to the APIM API Url Prefix.
            //In this case we set it to 'sample-app'. 
    
            var basePath = "sample-app"
            var serverUrl = $"{httpReq.Scheme}://{httpReq.Headers["X-Forwarded-Host"]}/{basePath}";
            swagger.Servers = new List<OpenApiServer> { new OpenApiServer { Url = serverUrl } };
         }
    });
})
.UseSwaggerUI(options =>
{
    options.RoutePrefix = string.Empty;
    options.SwaggerEndpoint("swagger/v1/swagger.json", "My Api (v1)");
});

The preceding code is an interim solution to use the Swagger UI  "Try it out" functionality when the API is deployed behind a reverse proxy (APIM) with API URL prefix / sub context configured.

The PreSerializeFilters option enable us to set some Swagger metadata based on the current request, here we set the Server Url to the expected value. This configures a filter that's executed prior to serializing the document.

Notice that I checked against httpReq.Headers.ContainsKey("X-Forwarded-Host") instead of checking if the host value is eqaul to "localhost". This means that we only change the Server Url when not running locally. The X-Forwarded-Host header should only be populated when the API is deployed to Azure App Service behind the proxy.

The rest of the code is pretty much self-explanatory and I've added a few comments to it for your reference.

We're using Swashbuckle.AspnetCore version 6.1.4 - which is the latest as of this time of writing and we're still having the same issue when our API is deployed in Azure and mapped through Azure Front Door and APIM.

With v6.1.x, the URL that you pass to SwaggerEndpoint is what the swagger-ui, a client-side application, uses to pull your API metadata. When the leading slash is omitted (swagger/v1/swagger.json), you're telling the swagger-ui that the path is relative to itself. This makes the Swagger UI agnostic to the base path to where it is running.

That's it. If you know a better solution to this, please let me know down in the comments section. I'll update this post as well once I figured a better way to handle this. Thank you for reading and I hope you find this post helpful.