Swashbuckle.WebApi
Swashbuckle.WebApi copied to clipboard
Swagger fails to load resources when my site is running under a virtual directory.
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?
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:
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.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();
});
}
}