0

So I have been writing up some ASP.NET Core training guides for my company, and came across an interesting bug on Windows.

When using Swashbuckle to implement Swagger, the application will crash on startup, during what appears to be the automatic DI registration of Swagger, due to a truncated path to the XML documentation file Swagger should target:

Exception has occurred: CLR/System.IO.FileNotFoundException
An unhandled exception of type 'System.IO.FileNotFoundException' occurred in Microsoft.AspNetCore.Hosting.dll: 'Could not find file 'C:\Temporary\Work\Swagger'.'
   at System.IO.FileStream.OpenHandle(FileMode mode, FileShare share, FileOptions options)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize)
   at System.Xml.XmlDownloadManager.GetStream(Uri uri, ICredentials credentials, IWebProxy proxy, RequestCachePolicy cachePolicy)
   at System.Xml.XmlUrlResolver.GetEntity(Uri absoluteUri, String role, Type ofObjectToReturn)
   at System.Xml.XmlTextReaderImpl.OpenUrl()
   at System.Xml.XmlTextReaderImpl.Read()
   at System.Xml.XPath.XPathDocument.LoadFromReader(XmlReader reader, XmlSpace space)
   at System.Xml.XPath.XPathDocument..ctor(String uri, XmlSpace space)
   at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions.<>c__DisplayClass31_0.<IncludeXmlComments>b__0()
   at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions.CreateSwaggerProvider(IServiceProvider serviceProvider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitTransient(TransientCallSite transientCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.<>c__DisplayClass22_0.<RealizeService>b__0(ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.Internal.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
   at Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass4_0.<UseMiddleware>b__0(RequestDelegate next)
   at Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder.Build()
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
   at SwaggerPathIssue.Program.BuildWebHost(String[] args) in C:\Temporary\Work\Swagger # Path Issue 2.0\Program.cs:line 18
   at SwaggerPathIssue.Program.Main(String[] args) in C:\Temporary\Work\Swagger # Path Issue 2.0\Program.cs:line 14

note the path listed in the exception is:

'C:\Temporary\Work\Swagger'

but actually should have been:

'C:\Temporary\Work\Swagger #1 Demo\doc\Swagger #1 Demo.xml'

the XML file to target is specified in Startup.cs as:

public void ConfigureServices(IServiceCollection services) {
  services.AddMvc();
  services.AddSwaggerGen(c => {
    c.SwaggerDoc("v1", new Info {
      Version = "v1",
      Title = "Swagger #1 Demo",
    });
    String xmlFile = $"{Assembly.GetEntryAssembly().GetName().Name}.xml";
    String xmlPath = Path.Combine("doc", xmlFile);
    c.IncludeXmlComments(xmlPath);
  });      
}

Removing the # from all the appropriate places (path, file names, launcher, etc.) gets everything working as expected.

Now if I don't specify a custom path at all, but rather generate the XML file in the location Swagger will look by default (using my .csproj file, or just by copying the XML file over to that location), the issue disappears.

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
  <DocumentationFile>bin\Debug\$(TargetFramework)\$(MSBuildProjectName).xml</DocumentationFile>
  <NoWarn>1701;1702;1705;1591</NoWarn>
</PropertyGroup>

However...

If I compile and run the original ASP.NET Core 2.0 project (with the #) on Ubuntu the issue is not present.

If I compile and run a ASP.NET Core 2.1 version of the project (with the #) on Windows the issue is not present.

So this appears to be a specific issue with ASP.NET Core 2.0, perhaps with DI registration using a parameter, or maybe with the how System.Xml is handling the path? I haven't dug that far in yet, but I wanted to put the issue out there for knowledge's sake while I take a deeper dive.

If anyone knows the root cause of this issue, please feel free to answer.

Xorcist
  • 3,129
  • 3
  • 21
  • 47
  • Quick workaround appears to be to convert the fully qualified file path to a **System.Uri** and then to access the **.AbsoluteUri** property to acquire a properly encoded file URI to then pass to **.IncludeXmlComments()**. That being said, this appears to be an issue with **System.Xml.XmlUrlResolver**. – Xorcist Jun 19 '18 at 18:34
  • Sounds like a bug in `IncludeXmlComments` you should fill an issue in the project: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues – Helder Sepulveda Jun 20 '18 at 20:23
  • Okay so... did a little more testing, and it appears that if an absolute path is provided to **.IncludeXmlComments()** it works as expected; without having to convert it to a **System.Uri** first. The issue only occurs if a relative path is provided. I'll dig a little deeper and most likely issue a bug if I find this to be a real issue. – Xorcist Jun 21 '18 at 21:37

0 Answers0