Swashbuckle.webapi: Swagger fails to load resources when my site is running under a virtual directory.

Created on 29 Apr 2015  路  30Comments  路  Source: domaindrivendev/Swashbuckle.WebApi

I noticed that everything is working fine when my site is running at (http://localhost:50000) but fails if I change my site to use a virtual directory (http://localhost/exceptionless).

Here is a link to our configuration: https://github.com/exceptionless/Exceptionless/blob/master/Source/Api/AppBuilder.cs#L148

Here is how I initially found out about this issue: https://github.com/exceptionless/Exceptionless/issues/101

Has anyone else ran into this issue? Any ideas on a work around for this behavior?

needs investigation

Most helpful comment

I had a similar problem with Swashbuckle.AspNetCore in my .NET Core Web API project; I was able to get the correct discovery URL to appear (and change the prefix) by adding the following to Configure():

app.UseSwagger(c =>
{
    c.RouteTemplate = "api-docs/{documentName}/swagger.json";
});
app.UseSwaggerUI(c =>
{
    c.RoutePrefix = "api-docs";
    c.SwaggerEndpoint("v1/swagger.json", "My API v1"); // will be relative to route prefix, which is itself relative to the application basepath
});

All 30 comments

Can you elaborate a little? For example, is your server even responding to the swagger endpoints (swagger/ui, swagger/docs/v1)? If so, are you seeing JavaScript errors in the swagger-ui?

It doesn't seem to be loading the swagger schema:

image

Hmmm - the virtual directory is missing in the discovery URL above, hence the 404. If you update the URL in the input field above to include it and then click "Explore", I imagine you'll get the docs to show up.

Let me know if this works?

If it does, you'll likely get a similar error if you subsequently "Try out" one of the operations because the same code is used to determine the API basePath in the Swagger json. Namely the following ...

    public static string DefaultRootUrlResolver(HttpRequestMessage request)
    {
        var scheme = GetHeaderValue(request, "X-Forwarded-Proto") ?? request.RequestUri.Scheme;
        var host = GetHeaderValue(request, "X-Forwarded-Host") ?? request.RequestUri.Host;
        var port = GetHeaderValue(request, "X-Forwarded-Port") ?? request.RequestUri.Port.ToString(CultureInfo.InvariantCulture);

        var httpConfiguration = request.GetConfiguration();
        var virtualPathRoot = httpConfiguration.VirtualPathRoot.TrimEnd('/');

        return string.Format("{0}://{1}:{2}{3}", scheme, host, port, virtualPathRoot);
    } 

Can't think of a good reason why virtualPathRoot would be empty here but it looks like that's what could be happening.

As a side note, you can override this logic in SwaggerConfig.cs via the RootUrl option. If you can figure out a better way to reliably determine the root url of your website, you can easily wire it up as a workaround.

Yeah, I'll take a look and let you know. I was hoping this would be handled by the underlying library as we are an open source project too and someone could be hosting this in really weird configurations.

Getting other reports of this issue (see referenced ticket). It seems httpConfig.VirtualPathRoot can't be relied on in all environments.

I'm going to try get something more robust in there for the next release

In the meantime, you can always wire up your own root url resolver via the RootUrl config setting (see readme)

Thanks for the heads up!

@domaindrivendev any ideas when this fix will go live?

Has this been fixed?

I was able to dynamically set the rooturl via the EnableSwagger callback:

c# c.RootUrl(req => new Uri(req.RequestUri, HttpContext.Current.Request.ApplicationPath ?? string.Empty).ToString());

@domaindrivendev any luck getting this resolved?

The only way I found to make it works is to add runAllManagedModulesForAllRequests="true" in the web.config... Otherwise, its doesn't read the Swagger JSON...

I also use the rooturi submitted by @spardo

Has there been any updates on this?

@niemyjski
c.RootUrl(req => { var pathBase = req.GetOwinContext().Get<string>("owin.RequestPathBase"); return new Uri(req.RequestUri, pathBase).ToString(); });
Works for me.

Be nice to have a more perm fix that works on .net core (non owin as well ) :)

how we can fix this in .net core

Not for OWIN:

c.RootUrl(req => req.RequestUri.GetLeftPart(UriPartial.Authority).TrimEnd('/') +
                 '/' +
                 req.GetRequestContext().VirtualPathRoot.TrimStart('/'));

For .NET Core use Swashbuckle.AspNetCore instead.

I have this issue on .net core / IIS 7.5, using the AspNetCore package

@Adriien-M's comment on issue #42 worked in my case when VirtualPathRoot wasn't:

c.RootUrl(req => req.RequestUri.GetLeftPart(UriPartial.Authority) + VirtualPathUtility.ToAbsolute("~/").TrimEnd('/'));

I had a similar problem with Swashbuckle.AspNetCore in my .NET Core Web API project; I was able to get the correct discovery URL to appear (and change the prefix) by adding the following to Configure():

app.UseSwagger(c =>
{
    c.RouteTemplate = "api-docs/{documentName}/swagger.json";
});
app.UseSwaggerUI(c =>
{
    c.RoutePrefix = "api-docs";
    c.SwaggerEndpoint("v1/swagger.json", "My API v1"); // will be relative to route prefix, which is itself relative to the application basepath
});

adding a relative path worked for me

            app.UseSwaggerUI(s => {
                s.RoutePrefix = "help";
                s.SwaggerEndpoint("../swagger/v1/swagger.json", "MySite");
                s.InjectStylesheet("../css/swagger.min.css");
            });

@scastaldi but did you have any problems with using the UI later? I came up with the same solution, but the "Try it!" buttons keep sending the request on domain only URLs.

@quilin I'm not sure what do you mean with domain only when you click "Try it out" button, everything it should be based on your current domain, from that everything should be based on the relative path

remember to add app.UseStaticFiles()
This is a more detail version of my code, I hope it helps...

        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();
            services.AddSwaggerGen(c => {
                c.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info { Title = "Salesforce Utility", Version = "v1" });
                var filePath = Path.Combine(PlatformServices.Default.Application.ApplicationBasePath, "SalesforceWebUtil.xml");
                c.IncludeXmlComments(filePath);
            });
            // initialize configuration
            string env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
            string e = ConfigurationHelper.GetEnvironment(new string[] { env });
            var conf = new ConfigurationHelper(Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath, e);
            Configuration = conf.Configuration; // just in case
            // inject the RestApiWrapperService as singleton into the services configuration
            var restService = new RestApiWrapperService(conf);
            services.AddSingleton<IRestApiWrapperService>(restService);
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            app.UseSwagger();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();
            // app.UseMvc();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });

            app.UseSwaggerUI(s => {
                s.RoutePrefix = "help";
                s.SwaggerEndpoint("../swagger/v1/swagger.json", "Salesforce Utility");
                s.InjectStylesheet("../css/swagger.min.css");
            });
        }

For me the following config work, the thing is that i had to deploy in two server, one with virtualDir (sub-folders) and the other no, bouth on IIS. This work for the two scenario:

        app.UseSwagger(c =>
        {
            c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
            {
                swaggerDoc.Host = httpReq.Host.Value;
                swaggerDoc.Schemes = new List<string>() {httpReq.Scheme};
                swaggerDoc.BasePath = httpReq.PathBase;
            });
        });

        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("./v1/swagger.json", "NewHotelBackOffice API V1");
            c.RoutePrefix = string.Empty;
        });

@miguelg1583
Where to add this configuration in project? I'm using spring mvc along with jersey

miguelg1583 suggestion works for me

app.UseSwagger(c =>
            {
                c.RouteTemplate = "api-docs/{documentName}/swagger.json";
            });
            app.UseSwaggerUI(c =>
            {
                c.RoutePrefix = "api-docs";
                c.SwaggerEndpoint("v1/swagger.json", "v1");

            });

adding a relative path worked for me

app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("./swagger/v1/swagger.json", "API V1");
c.RoutePrefix = string.Empty;
});

I discovered that {virtual directory}/swagger is added if you don't start the specification with a /, hence "v1/swagger.json" is the same as "/swagger/v1/swagger.json" when you don't have a virtual directory, but when you have one specifying it the first way will add it automatically. So the following solution works well:

app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("v1/swagger.json", "My Api v1");
});

@oyzar thanks work for me

app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("v1/swagger.json", "My Api v1");
});

Swagger is integrated into .net5 API project template.

 public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication3", Version = "v1" });
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication3 v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
Was this page helpful?
0 / 5 - 0 ratings

Related issues

benpriebe picture benpriebe  路  5Comments

josephearl picture josephearl  路  4Comments

guidoffm picture guidoffm  路  5Comments

johnpmcclung picture johnpmcclung  路  3Comments

qwertykeith picture qwertykeith  路  5Comments