4

I have a solution in which most of the projects are VB.net projects which all target the .NET Framework 4.8. All of those specific projects compile properly and are running in production.

I have added a C# project that targets .net standard 2.0. The primary reason for this particular project is to create a component that can process razor templates (given a model of course) in order to create output files using the RazorLight_wik8wz nuget package (I don't really care which package I use so long as I can render a razor template in memory because the output generated by the model/template pair may be persisted in different places in different cases).

In theory, and based on my understanding, I should be able to reference and use a .net standard 2.0 library project from my other .net framework 4.8 projects. However, I can't seem to get this to work (even though everything compiles and runs).

I've tried several different packages based on RazorLight--but I get similar results when I try to use them. I'm simply not sure if I'm encountering a bug or if I'm not doing something properly.

The C# project is named "JobOutputGenerator." I have added the nuget package called "RazorLight_wik8wz" and created an object called JobOutput that currently looks like this:

using System.Threading.Tasks;
using RazorLight;

public class JobOutput
{
    public static async Task<byte[]> CreateOutputAsync(string RazorTemplate, object Model)
    {
        RazorLightEngine engine = new RazorLightEngineBuilder().UseMemoryCachingProvider().Build();
        return System.Text.Encoding.ASCII.GetBytes(await engine.CompileRenderAsync(RazorTemplate.GetHashCode().ToString() + Model.GetHashCode().ToString(), RazorTemplate, Model));
    }

}

I have created another project called "TesterApp" which is a VB.NET project that targets .NET Framework 4.8. It is a simple windows form project that references JobOutputGenerator.

I have a button that does the following:

Private Sub btnRenderSample_Click(sender As Object, e As EventArgs) Handles btnRenderSample.Click
    Dim template As String = "Hello @Model.Name"
    Dim Model1 = New With {.Name = "John Doe"}
    Dim bytesTask = JobOutput.CreateOutputAsync(template, Model1)
    Dim str = System.Text.Encoding.ASCII.GetString(bytesTask.Result)
    MsgBox(str)
End Sub

When I launch the TesterApp in debug mode in Visual Studio, all projects compile just fine, and then when I click my sample button, Visual Studio breaks on the line (while highlighted in green):

Dim bytesTask = JobOutput.CreateOutputAsync(template, Model1)

with a box that is titled "Exception Unhandled" that says:

System.IO.FileNotFoundException: 'Could not load file or assembly 'RazorLight, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.'

I have run fuslogvw and TesterApp has no problem locating JobOutputGenerator.dll:

<meta http-equiv="Content-Type" content="charset=unicode-1-1-utf-8"><!-- saved from url=(0015)assemblybinder: --><html><pre>
*** Assembly Binder Log Entry  (6/7/2019 @ 9:37:08 AM) ***

The operation was successful.
Bind result: hr = 0x0. The operation completed successfully.

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
Running under executable  X:\Workspaces\SOLUTIONROOTFOLDER\TesterApp\bin\Debug\TesterApp.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: DisplayName = JobOutputGenerator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
 (Fully-specified)
LOG: Appbase = file:///X:/Workspaces/SOLUTIONROOTFOLDER/TesterApp/bin/Debug/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = TesterApp.exe
Calling assembly : TesterApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: X:\Workspaces\SOLUTIONROOTFOLDER\TesterApp\bin\Debug\TesterApp.exe.Config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///X:/Workspaces/SOLUTIONROOTFOLDER/TesterApp/bin/Debug/JobOutputGenerator.DLL.
LOG: Assembly download was successful. Attempting setup of file: X:\Workspaces\SOLUTIONROOTFOLDER\TesterApp\bin\Debug\JobOutputGenerator.dll
LOG: Entering run-from-source setup phase.
LOG: Assembly Name is: JobOutputGenerator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
LOG: Binding succeeds. Returns assembly from X:\Workspaces\SOLUTIONROOTFOLDER\TesterApp\bin\Debug\JobOutputGenerator.dll.
LOG: Assembly is loaded in default load context.

</pre></html>

I did, however, find this little "gem" in fuslogvw and I don't know what to do about it:

*** Assembly Binder Log Entry  (6/7/2019 @ 9:37:09 AM) ***

The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
Running under executable  X:\Workspaces\SOLUTIONROOTFOLDER\TesterApp\bin\Debug\TesterApp.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: DisplayName = RazorLight, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null
 (Fully-specified)
LOG: Appbase = file:///X:/Workspaces/SOLUTIONROOTFOLDER/TesterApp/bin/Debug/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = TesterApp.exe
Calling assembly : JobOutputGenerator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: X:\Workspaces\SOLUTIONROOTFOLDER\TesterApp\bin\Debug\TesterApp.exe.Config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///X:/Workspaces/SOLUTIONROOTFOLDER/TesterApp/bin/Debug/RazorLight.DLL.
LOG: Attempting download of new URL file:///X:/Workspaces/SOLUTIONROOTFOLDER/TesterApp/bin/Debug/RazorLight/RazorLight.DLL.
LOG: Attempting download of new URL file:///X:/Workspaces/SOLUTIONROOTFOLDER/TesterApp/bin/Debug/RazorLight.EXE.
LOG: Attempting download of new URL file:///X:/Workspaces/SOLUTIONROOTFOLDER/TesterApp/bin/Debug/RazorLight/RazorLight.EXE.
LOG: All probing URLs attempted and failed.

The following is the contents of the JobOutputGenerator.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup Label="Globals">
    <SccProjectName>SAK</SccProjectName>
    <SccProvider>SAK</SccProvider>
    <SccAuxPath>SAK</SccAuxPath>
    <SccLocalPath>SAK</SccLocalPath>
  </PropertyGroup>

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <PropertyGroup>
    <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
  </PropertyGroup>


  <ItemGroup>
    <PackageReference Include="ILRepack.MSBuild.Task" Version="2.0.1" />
    <PackageReference Include="RazorLight_wik8wz" Version="2.0.0" />
  </ItemGroup>

      <Target Name="ILRepack" AfterTargets="Build">

        <PropertyGroup>
            <WorkingDirectory>$(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework)</WorkingDirectory>
        </PropertyGroup>

        <ILRepack 
            OutputType="$(OutputType)" 
            MainAssembly="$(AssemblyName).dll" 
            OutputAssembly="$(AssemblyName).dll" 
            InputAssemblies="$(WorkingDirectory)\*.dll" 
            WilcardInputAssemblies="true"
            WorkingDirectory="$(WorkingDirectory)" />
    </Target>
</Project>
Dimitri Rodis
  • 142
  • 1
  • 8
  • https://stackoverflow.com/questions/43837638/how-to-get-net-core-projects-to-copy-nuget-references-to-build-output – Hans Passant Jun 06 '19 at 22:44
  • @Hans Passant - I came across that before and nothing I tried from that yielded any results. The compiled output from the Debug/.netstandard2.0 folder in JobOutputGenerator is being copied to the TesterApp/Debug folder and all of the files are there. – Dimitri Rodis Jun 07 '19 at 00:14
  • @Rufus L - thank you for fixing the formatting on my question. – Dimitri Rodis Jun 07 '19 at 00:15
  • Have you tried logging the .NET assembly resolution to see where it's looking for the file? Look for info on fuslogvw. – Craig Jun 07 '19 at 13:19
  • had this problem once too. if your winform-project uses the clasic csproj-format and you add a reference to a project that uses the newer format msbuild doesn't pick up the references referenced by the newer project to copy them to your winformsapp. my quick workaround was to add the nuget-reference to the winforms-project as well to get this fixed. there might be a better solution than this but i haven't found it yet – D.J. Jun 07 '19 at 16:53
  • @Craig - added relevant output from fuslogvw above. It is, more or less, giving me the same info as the exception box in Visual Studio--except that it is showing the files it is looking for, which do not exist anywhere at all--and I don't know why that would be the case. I've tried more than one RazorLight based package and the behavior is the same. – Dimitri Rodis Jun 07 '19 at 16:54
  • @D.J. Thanks for the suggestion but I don't see how that would solve the problem. The Winforms app is just a test harness--I can't just go adding references to all of the other projects to make this work. Apparently the way the project is compiling it isn't including the relevant nuget outputs or compiling in the package. Ultimately JobOutputGenerator.dll will need to be a single DLL with its dependencies rolled into a single assembly via ILMerge or similar tool. – Dimitri Rodis Jun 07 '19 at 16:57
  • checkout this, it might help if i read the setup correctly: https://github.com/dotnet/standard/issues/410#issuecomment-323130308 – D.J. Jun 07 '19 at 17:00
  • It's showing you where it's looking for the missing assembly. If it isn't there, then something's wrong---either you meant to copy it and didn't, or there's some sort of configuration problem leading it to look in the wrong place. In the extreme, you might need to intercept assembly resolution in your app and redirect to the correct location. – Craig Jun 07 '19 at 17:13
  • https://github.com/toddams/RazorLight#faq – Daniel A. White Jun 07 '19 at 17:13
  • @D.J. Added contents of JobOutputGenerator.csproj above. I added the lines based on your comment (which I came across previously) and I didn't recall what difference it made, so I went ahead and put it back in. It made no difference that I could find--the error message is still the same. Also the same whether ILRepack is there or not--like I said, ultimately this DLL needs to be self contained so that's why ILRepack is there. – Dimitri Rodis Jun 07 '19 at 17:13
  • @DanielA.White That's not the message I'm getting--and I previously tried that also when I was using the "straight up" RazorLight package which apparently has some issue with .net standard 2.0 where some of the alternatives (like the one I am using) do not. – Dimitri Rodis Jun 07 '19 at 17:15
  • @Craig I agree there is something wrong, but I don't know what--which is why I'm here. It is as if Visual Studio is not compiling or including the nuget package even though it is referenced and there are no compilation errors--I receive the error that I receive at run time. – Dimitri Rodis Jun 07 '19 at 17:17

1 Answers1

2

So after more searches I came across the following problem and set of posts. Following this workaround seems to have solved my problem for now.

Apparently this has been a problem for quite some time (since 2017--thanks Microsoft!) without a permanent fix.

The problem was first reported here:

NuGet dependencies of .NET Standard library referenced from classic .NET app cannot be resolved at runtime with FileNotFoundException #1582

If you read through this string of messages, you eventually come to this workaround near the bottom, with others confirming this person's findings. Following this workaround causes the DLL files to all get built/copied to the Debug folder and now the referenced DLL is no longer "missing" and the exception I was receiving at runtime is gone.

I made this modification to my JobOutputGenerator .csproj file, except that I used ;net48 instead of ;net461 .

NOTE THE TAG:

<TargetFramework>

needs to change to (note the "s")

<TargetFrameworks>

in order for this to work--otherwise your project won't load properly in Visual Studio.

Dimitri Rodis
  • 142
  • 1
  • 8