Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I setup Swashbuckle v5 with swagger when I have a custom base url?

I am upgrading a .net API to .net Core 3.1 and using Swashbuckle.AspNetcore 5.4.1. The API is running inside a ServiceFabric app. I found this https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1173 and tried to follow that and swagger gets generated but if I try to use the Swagger UI to send requests the request URL is with the wrong IP so the request fail. In the old Swashbuckle 4.0.1 setup we did not specify host, only the relative basePath. How can I achieve the same?

Startup.cs

var swaggerBasePath = "/MySfApp/SfApp.ClientApi/";

app.UseSwagger(c =>
{
    c.SerializeAsV2 = serializeAsSwaggerV2;
    
    c.RouteTemplate = "swagger/{documentName}/swagger.json";
    c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
    {
        swaggerDoc.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}{swaggerBasePath}" } };
    });
});

app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("api/swagger.json", "My API V1");
});

The result is that the Swagger UI loads correctly on URL:

http://145.12.23.1:54000/MySfApp/SfApp.ClientApi/swagger/index.html

and it says under name that BaseUrl is:

[ Base URL: 10.0.0.4:10680/MySfApp/SfApp.ClientApi/ ]

The 10.0.0.4:10680 is the node inside the ServiceFabric cluster. Correct IP to reach from outside is 145.12.23.1:54000. In the older version (4.0.1) of Swashbuckle it says baseUrl without IP first: "/MySfApp/SfApp.ClientApi"

Swagger.json is located at:

http://40.68.213.118:19081/MySfApp/SfApp.ClientApi/swagger/api/swagger.json

and it says:

"swagger": "2.0",
... 
"host": "10.0.0.4:10680",  
"basePath": "/MySfApp/SfApp.ClientApi/",
"schemes": [
"http"
],
"paths": {
"/activity/{activityId}": {
"get"
...etc

If i try to send a GET request from the Swagger UI the request is sent to wrong IP:

curl -X GET "http://10.0.0.4:10680/MySfApp/MySfApp/activity/3443"

EDIT 1: After some digging I have now changed the setup to this in startup.cs

var swaggerBasePath = "/MySfApp/SfApp.ClientApi/";
app.UsePathBase($"/{swaggerBasePath}");
app.UseMvc();
app.UseSwagger(c =>
{
    c.SerializeAsV2 = serializeAsSwaggerV2;

    c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
    {
        if (!httpReq.Headers.ContainsKey("X-Original-Host")) 
            return;

        var serverUrl = $"{httpReq.Headers["X-Original-Proto"]}://" +
                        $"{httpReq.Headers["X-Original-Host"]}/" +
                        $"{httpReq.Headers["X-Original-Prefix"]}";

        swaggerDoc.Servers = new List<OpenApiServer>()
        {
            new OpenApiServer { Url = serverUrl }
        };
    });
});
app.UseSwaggerUI(options => {
    options.SwaggerEndpoint("api/swagger.json", "My API V1");
});

This now leads to the Swagger UI loading properly with the baseUrl

http://145.12.23.1:54000/MySfApp/SfApp.ClientApi/swagger/index.html

and also swagger.json is served correctly with the correct baseUrl.

http://145.12.23.1:54000/MySfApp/SfApp.ClientApi/swagger/api/swagger.json

So the wrong hostname is resolved. Thanks to idea from this thread.

However when I try to call an endpoint from the Swagger UI page, the curl URL does not include the baseUrl. So closer... but currently not possible to use Swagger UI.

curl -X GET "http://10.0.0.4:10680/activity/3443"

The swagger.json does not have 'host' nor 'basePath' defined.

like image 636
Lisa-Marie Avatar asked May 11 '20 15:05

Lisa-Marie


People also ask

What is swagger default URL?

Launch the app, and navigate to http://localhost:<port>/swagger/v1/swagger.json . The generated document describing the endpoints appears as shown in OpenAPI specification (openapi. json). The Swagger UI can be found at http://localhost:<port>/swagger .


2 Answers

We're using Swashbuckle 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 App Service that is mapped through Azure Front Door and APIM. The "Try out" functionality does not work as the base path / api route prefix is stripped from the Swagger UI. For example,

Instead of https://{DOMAIN}.com/{BASEPATH}/v1/Foo, the Swagger UI uses this: https://{DOMAIN}.com/v1/Foo. You can see that the /BASEPATH is missing.

I spent the whole day trying to fix this with trial and error, trying various approaches with no luck, I couldn't get an elegant way to get the base path from swagger configuration. For the time being, here's what I did to fix it:

app.UseSwagger(options =>
{
    //Workaround to use the Swagger UI "Try Out" functionality when deployed behind a reverse proxy (APIM) with API prefix /sub context configured
    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 returning as null/blank. Perhaps this has something to do with how the proxy is configured which we 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)");
});

Here's an open discussion related to this issue here.

like image 66
Vincent Maverick Durano Avatar answered Oct 14 '22 17:10

Vincent Maverick Durano


I were having something similar in my solution and I have used a little bit this way and that works well for me, in case that helps someone.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
   var pathBase = Configuration["PATH_BASE"];
   if (!string.IsNullOrWhiteSpace(pathBase))
   { 
      app.UsePathBase($"/{pathBase.TrimStart('/')}");
      app.Use((context, next) =>
      {
          context.Request.PathBase = new PathString($"/{pathBase.TrimStart('/')}");
          return next();
      });

    if (env.IsDevelopment())
    {
       app.UseSwagger(c =>
       {
           c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
           {
                 if (!httpReq.Headers.ContainsKey("X-Original-Host"))
                 return;

                 var serverUrl = $"{httpReq.Headers["X-Original-Proto"]}://" + $"{httpReq.Headers["X-Original-Host"]}/" + $"{httpReq.Headers["X-Original-Prefix"]}";

                 swaggerDoc.Servers = new List<OpenApiServer>()
                 {
                     new OpenApiServer { Url = serverUrl }
                 }
           });
       });
       app.UseSwaggerUI(c => c.SwaggerEndpoint($"/{pathBase.TrimStart('/')}/swagger/v1/swagger.json", "My.API v1"));    
    }
   }
}

check the last line app.UseSwaggerUI(c => c.SwaggerEndpoint($"/{pathBase.TrimStart('/')}/swagger/v1/swagger.json", "My.API v1"));

like image 20
Brijesh Shah Avatar answered Oct 14 '22 17:10

Brijesh Shah