HakanL / WkHtmlToPdf-DotNet

C# .NET Core wrapper for wkhtmltopdf library that uses Webkit engine to convert HTML pages to PDF.
GNU Lesser General Public License v3.0
367 stars 66 forks source link

Error running on Azure (An attempt was made to load a program with an incorrect format.) #8

Closed simbrams closed 4 years ago

simbrams commented 5 years ago

Everything works fine locally (I am running on macos), but then after deploying my solution to azure I get this error An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B). It seems like @HakanL said that it doesn't use the right library 32/64 version. I tried to Modify in my App service parameters to switch the machine to 64bit environment, but it has no effect, I still get the error.

simbrams commented 5 years ago

Ok after a Day of research I managed to fix the issue. First of all, I downloaded all the libraries version (32 & 64 bit) and specified in my .csproj the following:

<ItemGroup> 
      <None Include="GeneratedPdf\**">
        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      </None>

      <None Include="libs\\32 bit\\libwkhtmltox.dll">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </None>
      <None Include="libs\\32 bit\\libwkhtmltox.dylib">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </None>
      <None Include="libs\\32 bit\\libwkhtmltox.so">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </None>

      <None Include="libs\\64 bit\\libwkhtmltox.dll">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </None>
      <None Include="libs\\64 bit\\libwkhtmltox.dylib">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </None>
      <None Include="libs\\64 bit\\libwkhtmltox.so">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      </None>
  </ItemGroup>

Those lines will make sure that your publish directory will contains all the following files and directories.

After that in you Startup class, please include the following internal class:

internal class CustomAssemblyLoadContext : AssemblyLoadContext
{
     public IntPtr LoadUnmanagedLibrary(string absolutePath)
     {
         return LoadUnmanagedDll(absolutePath);
     }
     protected override IntPtr LoadUnmanagedDll(String unmanagedDllName)
     {
         return LoadUnmanagedDllFromPath(unmanagedDllName);
     }
     protected override Assembly Load(AssemblyName assemblyName)
     {
         throw new NotImplementedException();
     }
}

Then to finish before including your PdfTools as follow:

services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools()));

add the following instead:

CustomAssemblyLoadContext context = new CustomAssemblyLoadContext();
var architectureFolder = (IntPtr.Size == 8) ? "64 bit" : "32 bit";

if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
         var wkHtmlToPdfPath = Path.Combine(Directory.GetCurrentDirectory(), $"libs\\{architectureFolder}\\libwkhtmltox.dylib");
          context.LoadUnmanagedLibrary(wkHtmlToPdfPath);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
         var wkHtmlToPdfPath = Path.Combine(Directory.GetCurrentDirectory(), $"libs\\{architectureFolder}\\libwkhtmltox.so");
         context.LoadUnmanagedLibrary(wkHtmlToPdfPath);
}
else // RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
{
        var wkHtmlToPdfPath = Path.Combine(Directory.GetCurrentDirectory(), $"libs\\{architectureFolder}\\libwkhtmltox.dll");
        context.LoadUnmanagedLibrary(wkHtmlToPdfPath);
}
services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools()));

It will check your runtime version and your OS in order to load to right Library manually.

You can let Azure default settings (32bit) This is working on .NET Core 2.2. I am deploying on Azure through Azure Devops. Tested on MacOSX and Windows 10 both 64

HakanL commented 5 years ago

@simbrams Can you submit a PR?

HakanL commented 5 years ago

Oh, you had to add the binaries to your main project, not the DinkToPdf package?

simbrams commented 5 years ago

Oh, you had to add the binaries to your main project, not the DinkToPdf package?

Exactly, I added them in a subfolder

HakanL commented 5 years ago

Oh I see. That shouldn't be necessary, the goal with the DinkToPdf library is that it's all embedded in the nuget package, just like other packages like Sqlite for example.

huysentruitw commented 4 years ago

What if we would add wkhtmltopdf as embedded resources and unpack them at application startup? I've seen IronPDF doing this.

HakanL commented 4 years ago

We can try, but I don't think the issue is with the binaries, but with how the sandbox in Azure operates.

drdamour commented 4 years ago

did a way to use nuget and get these dll's in place ever materialize for an azure deploy?

HakanL commented 4 years ago

Unfortunately I haven't had a chance to try it, let's hope for some time over next year!

HakanL commented 4 years ago

I'm running a Web App in Azure, 64-bit .NET Core 3.1 in an S2 App Service Plan and DinkToPdf is working fine. It may not be possible to run in a consumption plan, or Azure function, but in a regular plan it seems to work fine, at least in 64-bit.

drdamour commented 4 years ago

It works but didnt seem like the huget worked without cooying files by hand can you enumerate your steps?

HakanL commented 4 years ago

I didn't have to copy any files manually, I literally just added the NuGet package (Haukcode.DinkToPdf) and deployed to Azure App Services. If you have to copy the binaries then it sounds like you have something else going on. I didn't test with 32-bit though, I've only tested with .NET Core 3.1 64-bit (on Azure, locally I've tested both 32 and 64 bit).

PiotrFirst commented 4 years ago

I installed Haukcode.DinkToPdf as HakanL said. Then I still getting error during load project on AZURE. I commented this line which is responsible for loading lib file: // context.LoadUnmanagedLibrary(Path.Combine(Directory.GetCurrentDirectory(), "libwkhtmltox.dll")); I don't know why but project run on AZURE web service.

HakanL commented 4 years ago

Does it work after you uncomment that line (or is it just loading)? What are the specifics about your Azure web service, 32/64 bit, .NET version, consumption plan, etc?

drdamour commented 4 years ago

@HakanL something is amiss and you've left out some important things.

how did you deploy to azure? using built in VS publish or devops pipeline or kudu build?

also what architecture do you have your project set to.

when i bring in that package and i run a publish to folder i don't see the libwkhtmltox anywhere. i think your test might have been from a dirty state.

HakanL commented 4 years ago

I deploy using a pipeline on Visual Studio online (DevOps). 64-bit arch .NET Core 3.1. The deployment is completely automated, not dirty state. Are you sure you're using the Haukcode NuGet package and not the original?

drdamour commented 4 years ago

yes i'm sure.

ok you answered the missing piece, since you are building on 64 bit you are targeting 64 bit (implicitly, dirty) so dotnet publish will copy them over....as compared to an AnyCPU build which would not.

thx for explaining

HakanL commented 4 years ago

I'm not targeting a particular arch, I just do dotnet publish, so it's framework-dependent. But my Web App (in Azure portal) is configured for 64-bit. If I look in the runtimes folder (on the Azure server) I have the native binaries for all the various architectures (win-x64, win-x86) with the wkhtmltox.dll files in there. I also just tried to switch it to 32-bit (in the Azure portal) without deployment, and it works. So I know it's not dirty or targeting a particular architecture. FWIW AnyCPU isn't something .NET Core is using, are you targeting the full .NET Framework? I haven't tested that.

drdamour commented 4 years ago

i'm using .net core and anycpu IS an option for web applications (but not executables, you are correct). You ARE implicitly picking an architecture in our build before getting it up to azure (or maybe when kudu goes to launch it...i'm not totally sure)

anyways your missing details clarified what was going on in the very complicated build pipeline i'm subjected to for this app. thanks again!

On Sun, Jan 26, 2020 at 5:42 PM Hakan Lindestaf notifications@github.com wrote:

I'm not targeting a particular arch, I just do dotnet publish, so it's framework-dependent. But my Web App (in Azure portal) is configured for 64-bit. If I look in the runtimes folder (on the Azure server) I have the native binaries for all the various architectures (win-x64, win-x86) with the wkhtmltox.dll files in there. I also just tried to switch it to 32-bit (in the Azure portal) without deployment, and it works. So I know it's not dirty or targeting a particular architecture. FWIW AnyCPU isn't something .NET Core is using, are you targeting the full .NET Framework? I haven't tested that.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/HakanL/DinkToPdf/issues/8?email_source=notifications&email_token=AALRYADJ5HKZKJLAT2HVD5LQ7YUV7A5CNFSM4H2VNMD2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEJ6CEXQ#issuecomment-578560606, or unsubscribe https://github.com/notifications/unsubscribe-auth/AALRYAFZS2BTCKSVFHGJVVDQ7YUV7ANCNFSM4H2VNMDQ .

HakanL commented 4 years ago

IIS in Azure will load it in either a 32 or 64-bit process, but all files are included to run in either (in fact even files for linux and mac are included in the package when publishing it, it really is completely non-specific architecture). Good that you have enough details though!

ShahryarSaljoughi commented 4 years ago

what is the `

Always

`

part for? actually what is GeneratedPdf ?

HakanL commented 4 years ago

Those were in his main project, not in DinkToPdf. But that should not be necessary.

drdamour commented 4 years ago

That was our workaround before we figured out the targeting was necessary (for our build it was)