RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.79k stars 1.29k forks source link

Issue with generating SDK from .NET Core assembly. #1159

Open Boriszn opened 6 years ago

Boriszn commented 6 years ago

Hi Rico,

Could you please help.

When I try to generate SDK from .net core assembly project it rises an exception:

tem.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.AspNetCore.JsonPatch, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' or one of its dependencies. The system cannot find the file specified.
File name: 'Microsoft.AspNetCore.JsonPatch, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'

Check if .NET Core is installed and 'dotnet' is globally available.

But the assembly exists.

RicoSuter commented 6 years ago

Try adding

%USERPROFILE%/.nuget/packages

to the ReferencePaths

RicoSuter commented 6 years ago

v11.14.0:

We improved the assembly loader (mainly for .NET Core) so that it runs in a more isolated space and the loaded DLLs should better match the requested versions. Please test this with your projects to ensure that we didnt introduce regressions.

Important if you have DLL loading problems:

For more information regarding assembly loading: https://github.com/RSuter/NSwag/wiki/Assembly-loading

Main commit: https://github.com/RSuter/NSwag/commit/04576e4d1b714ac5bd9b5df11d72469a23f7c0ff#diff-14dafe6661bab407ae5c0d7095ccd1f4

slang25 commented 6 years ago

We found referencing the .nuget folder problematic as you cannot guarantee you'll pick up the correct version of the assembly.

Try:

<Copy SourceFiles="@(Reference)" DestinationFolder="$(OutDir)WebApi2SwaggerReferencePath" />

And point to WebApi2SwaggerReferencePath in ReferencePaths.

The later cleanup with:

<RemoveDir Directories="$(OutDir)WebApi2SwaggerReferencePath" />

Once we adopted this, life was bliss.

RicoSuter commented 6 years ago

Correct, in versions prior to v11.14 it was often the problem that the wrong DLL was loaded. In the latest version (v11.14) I try to find the best version based on the requested version:

https://github.com/RSuter/NSwag/blob/master/src/NSwag.AssemblyLoader/AssemblyLoader.cs#L133

However, this also does not work very well because some DLLs/package versions are strangely missing from the cache (even if they are used) and also scanning the cache directory takes a lot of time...

So using the nuget package cache directory is just a shortcut to test if assembly loading can work...

slang25 commented 6 years ago

I'd suggest recommending the above approach, it has been bullet proof for us, and you don't need to publish and build again, it's all done in one hit.

RicoSuter commented 6 years ago

What is @(Reference)?

slang25 commented 6 years ago

That is the resolved references from the .NET build target in MSBuild.

By the way, this library has saved us many hours in our current project, so a massive thank you is due!

slang25 commented 6 years ago

You would put that in a target with AfterTargets="Build"

RicoSuter commented 6 years ago

I think it is not needed to add WebApi2SwaggerReferencePath to ReferencePaths as NSwag automatically searches in the directory where the specified assembly is located...

RicoSuter commented 6 years ago

But thanks, I'll try that... looks promising..

RicoSuter commented 6 years ago

Is it also working with v11.14?

slang25 commented 6 years ago

In projects that use the new PackageReference, i.e. .NET Core projects, the @(Reference) will be an item group where all of the assemblies are scatter in the .nuget folder, and quickly exceed the max length to pass to NSwag, the copy puts them all in 1 folder for that purpose. Alse in a regular build the output folder won't have the framework assemblies, but they will be in the @(Reference) for example.

We researched this extensively and believe this is the optimal solution.

slang25 commented 6 years ago

Haven't testing in v11.14, but it should work as it puts all of the required assemblies in a folder and says "hey use these", and they are ones you want.

slang25 commented 6 years ago

If there was a version of NSwag.MSBuild that didn't just shell out, you might be able to avoid that copy too.

RicoSuter commented 6 years ago

Wow, this is really amazing!

  <Target Name="NSwag" AfterTargets="Build">
    <Copy SourceFiles="@(Reference)" DestinationFolder="$(OutDir)References" />
    <Exec Command="$(NSwagExe_Core20) run nswag.json /variables:Configuration=$(Configuration)" />
    <RemoveDir Directories="$(OutDir)References" />
  </Target>
slang25 commented 6 years ago

Yeah, we have been using this for months (sorry for keeping it a secret), and it's lovely

slang25 commented 6 years ago

Credit to @shaynevanasperen who reduced our solution to that little nugget

RicoSuter commented 6 years ago

Yes, its much better than using "dotnet publish" with

<PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
comdw commented 6 years ago

Is there a way to make use of the Configuration variable in nswag.json? Currently I have the assemblyPaths as bin/Debug/netcoreapp2.0/<assembly>.dll so this only works when building in Debug and not when building Release.

RicoSuter commented 6 years ago

In the nswag.json set

"defaultVariables": "Configuration=Debug"

and in nswag.json use the placeholder

$(Configuration)

in the .csproj task use

    <Exec Command="$(NSwagExe_Core20) run nswag.json /variables:Configuration=$(Configuration)" />
comdw commented 6 years ago

Did you mean a different file for defaultVariables="Configuration=Debug" as the format is wrong for nswag.json?

I tried it anyway as "defaultVariables": "Configuration=Debug" and I already have the .csproj task as you stated above. I'm getting error "Could not find a part of the path ...\bin\$(Configuration)\netcoreapp2.0" when building.

RicoSuter commented 6 years ago

Sorry, it's "defaultVariables": "Configuration=Debug" in nswag.json... This only works in v11.14+

RicoSuter commented 6 years ago

image

comdw commented 6 years ago

Ok yep, I had too early a version. It works fine when I upgraded to v11.14.1, but I get an different error for v11.15 (not sure if this has been reported elsewhere):

2>Executing file 'nswag.json'...
2>System.BadImageFormatException: Could not load file or assembly 'System.Runtime, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Reference assemblies should not be loaded for execution.  They can only be loaded in the Reflection-only loader context. (Exception from HRESULT: 0x80131058)
2>File name: 'System.Runtime, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' ---> System.BadImageFormatException: Cannot load a reference assembly for execution.
2>   at System.Reflection.RuntimeAssembly.GetExportedTypes(RuntimeAssembly assembly, ObjectHandleOnStack retTypes)
2>   at System.Reflection.RuntimeAssembly.GetExportedTypes()
2>   at NSwag.SwaggerGeneration.WebApi.WebApiToSwaggerGenerator.GetControllerClasses(Assembly assembly) in C:\projects\nswag\src\NSwag.SwaggerGeneration.WebApi\WebApiToSwaggerGenerator.cs:line 50
2>   at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.MoveNext()
2>   at System.Linq.Enumerable.SelectEnumerableIterator`2.ToArray()
2>   at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
2>   at System.Linq.OrderedEnumerable`1.ToArray()
2>   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
2>   at NSwag.Commands.SwaggerGeneration.WebApiToSwaggerCommand.GetControllerNames(AssemblyLoader assemblyLoader) in C:\projects\nswag\src\NSwag.Commands\Commands\SwaggerGeneration\WebApiToSwaggerCommand.cs:line 261
2>   at NSwag.Commands.SwaggerGeneration.WebApiToSwaggerCommand.<RunIsolatedAsync>d__88.MoveNext() in C:\projects\nswag\src\NSwag.Commands\Commands\SwaggerGeneration\WebApiToSwaggerCommand.cs:line 185
RicoSuter commented 6 years ago

how are you executing nswag? via NSwag.MSBuild? do you also copy the references or publish first?

comdw commented 6 years ago

Yes NSwag.MSBuild, same as your example above:

  <Target Name="NSwag" AfterTargets="Build">
    <RemoveDir Directories="$(OutDir)References" />
    <Copy SourceFiles="@(Reference)" DestinationFolder="$(OutDir)References" />
    <Exec Command="$(NSwagExe_Core20) run nswag.json /variables:Configuration=$(Configuration)" />
  </Target>

Not publishing first.

RicoSuter commented 6 years ago

Can you provide a sample project where i can reproduce this?

comdw commented 6 years ago

Sure, see attached. The build fails unless I downgrade the NSwag packages to 11.14.1. NSwagIssue.zip

RicoSuter commented 6 years ago

Please retry with v11.15.1

comdw commented 6 years ago

Works great thank you!

RicoSuter commented 6 years ago

@Boriszn and @slang25 Is it working for you too?

slang25 commented 6 years ago

It works for us as we use the @References copy approach, I think this makes sense when using from a csproj.

The assembly loading improvements are fantastic if running separately.

electricessence commented 6 years ago

So I've encountered a couple of issues here. (Possibly a new problem or at least recent) [ASP.NET Core 2 WebApi Application]

I can get my config to render if done through NSwagStudio. (added %USERPROFILE%\.nuget\packages reference to get it to work.)

But after I added NSwag.MSBuild, I get this:

1>System.IO.FileNotFoundException: Could not load file or assembly 'System.ServiceModel.Primitives, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.
1>File name: 'System.ServiceModel.Primitives, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' ---> System.IO.FileNotFoundException: Could not load the specified file.
... etc ...

And from then on could not load assemblies using NSwagStudio. :(

To correct this, I removed the NSwag.MSBuild reference, deleted the bin & obj folders, republished locallay, and it would then load the assemblies again.

As @comdw commented, I also encountered the same error there (BadImageFormatException) and had to blow away my entire repo to fix. Problem would not go away.

If I remove System.ServiceModel.Primitives dependency, this problem also goes away. :(

RicoSuter commented 6 years ago

@electricessence do you use the correct binary?

electricessence commented 6 years ago

@RSuter. When I was investigating this (still unresolved), I had the latest version from nuget. It was as if the DLL hint was wrong. But again, I did this a few times from a blown away repo.

cvallance commented 6 years ago

@RSuter & @slang25 - this was causing an issue in our project... we reference a dll that we have locally. So we have something like:

<Reference Include="OurNamespace.ServerClient, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
  <HintPath>..\ExternalLibraries\OurNamespace.ServerClient.dll</HintPath>
</Reference>

But now the @(Reference) contains the string OurNamespace.ServerClient, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null which didn't work with Copy because it isn't a file. I think it's the same issue @electricessence was running into.

Anyway, to cut a long story short, this fixed it:

<Copy SourceFiles="@(ReferencePath)" DestinationFolder="$(OutDir)References" />

Let me know if that's going to mess anything up... otherwise I suggest you update the documentation.

slang25 commented 6 years ago

@cvallance Nice find, looking at this again, I cannot find much info about Reference, however ReferencePath seems to be the correct property to use, and is the primary output of ResolveAssemblyReferences, so I think that would be the more correct property to use.

I think it's worth updating the docs :+1:

RicoSuter commented 6 years ago

@slang25 looks good to me. Pleas update the docs if you have time...

praveenv4k commented 6 years ago

@cvallance Your suggestion really saved my day! Thank you! @RSuter I love using NSwag :)

simeyla commented 5 years ago

@cvallance So I read this thread months ago, but I didn't realize the subtle difference between these two

<Copy SourceFiles="@(Reference)" DestinationFolder="$(OutDir)References" />

and - the one that works:

<Copy SourceFiles="@(ReferencePath)" DestinationFolder="$(OutDir)References" />

Interestingly for me the first would work OK with a Build, but not a Rebuild All. I don't fully understand why, I could guess - but trying to explain my guess here wouldn't be productive here!

slang25 commented 5 years ago

If you want to start making sense of this sort of stuff, I recommend turning on binary logging and using MSBuildStructuredLog, you just add /bl to your msbuild command and you'll get a msbuild.binlog you can open and make sense of.

So here you'd run msbuild /t:Rebuild /bl