Skip to content

Fix "FileNotFoundException (Could not load file or assembly ‘System.Net.Http, Version=4.0.0.0’ . . .)"#880

Merged
zacharycmontoya merged 2 commits into
masterfrom
zach/fix/wcf-http-dependency
Aug 27, 2020
Merged

Fix "FileNotFoundException (Could not load file or assembly ‘System.Net.Http, Version=4.0.0.0’ . . .)"#880
zacharycmontoya merged 2 commits into
masterfrom
zach/fix/wcf-http-dependency

Conversation

@zacharycmontoya

Copy link
Copy Markdown
Contributor

Issue

Beginning with .NET Tracer v1.18.0, the following error message begins occurring: Could not load file or assembly 'System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified. This occurs in the following conditions:

  • The application targets .NET Framework 4.6 or higher
    • .NET Core is unaffected
  • The application references the System.Net.Http NuGet package
  • The application has a binding redirect on System.Net.Http to load a version higher than 4.0.0.0

This occurs because the .NET Tracer still has a transitive dependency on System.Net.Http, Version=4.0.0.0 by having an assembly reference to System.ServiceModel, Version=4.0.0.0. This transitive dependency on System.Net.Http was not an issue until version 1.18.0 of the .NET Tracer when the .NET Tracer began implementing a new CLR Profiler callback called ICorProfilerCallback6::GetAssemblyReferences (see #876 for further background). This new behavior results in the .NET runtime eagerly loading the entire assembly closure for a given assembly to ensure that the domain-neutral decision made at the time of loading is correct. In our case, the assembly closure walk of Datadog.Trace.ClrProfiler.Managed triggers an eager load of System.Net.Http v4.0.0.0. However, this load fails, and all subsequent loads of System.Net.Http v4.0.0.0 return with the cached load failure, even though they should be redirected to the version specified in the web.config.

Changes

  • Remove assembly reference to System.ServiceModel.dll in Datadog.Trace.ClrProfiler.Managed.dll
  • Modify WCF automatic instrumentation to dynamically access fields / properties on WCF objects
  • Add a test for the WCF automatic instrumentation

@DataDog/apm-dotnet

…with the WcfIntegration. This will not run in CI but can be run manually.

Additionally, modify the Samples.Wcf to accept a port and timeout to better prepare it for CI.
…naged because it has a downstream dependency on System.Net.Http.

Reimplement the WcfIntegration to use reflection instead of directly compiling against WCF types.
@zacharycmontoya zacharycmontoya added type:bug area:automatic-instrumentation Automatic instrumentation managed C# code (Datadog.Trace.ClrProfiler.Managed) labels Aug 27, 2020
@zacharycmontoya zacharycmontoya self-assigned this Aug 27, 2020
@zacharycmontoya
zacharycmontoya marked this pull request as ready for review August 27, 2020 00:27
@zacharycmontoya
zacharycmontoya requested a review from a team as a code owner August 27, 2020 00:27
zacharycmontoya added a commit that referenced this pull request Aug 27, 2020
…ild (#881)

After working on #880 (which focuses on System.Net.Http), it seems especially dangerous to even have an assembly reference on an assembly that can be provided by NuGet instead of the GAC. After further testing, the same System.Net.Http failure mode can occur with System.Diagnostics.DiagnosticSource if there is a binding redirect on the assembly and one of the assemblies in the application was compiled against the same version as the .NET Tracer (System.Diagnostics.DiagnosticSource Version=4.0.2.1).

Since we do not use System.Diagnostics.DiagnosticSource right now on .NET Framework, this change removes it from the build to get rid of the possibility of assembly loading issues. Properly loading this on .NET Framework will be addressed when we begin moving towards using the Activity class to propagate events.
scope = tracer.StartActive("wcf.request", propagatedContext);
var span = scope.Span;

object requestHeaders = requestMessage.GetProperty<object>("Headers").GetValueOrDefault();

@lucaspimentel lucaspimentel Aug 27, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change this, but for future reference if you only access properties from requestHeaders and don't need the value itself, you can chain multiple GetProperty() without intermediate GetValueOrDefault() calls.

Ex:

var foo = value.GetProperty(..).GetProperty(..).GetProperty(..).GetValueOrDefault();
Suggested change
object requestHeaders = requestMessage.GetProperty<object>("Headers").GetValueOrDefault();
object requestHeaders = requestMessage.GetProperty<object>("Headers");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. I wasn't sure what would be more performant: Calling GetProperty on the type MemberResult<T> instance (which is what the chain does) or calling GetProperty on the type object instance (which is what I ended up doing by calling GetValueOrDefault to return a result). Maybe we should benchmark the two of them.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:automatic-instrumentation Automatic instrumentation managed C# code (Datadog.Trace.ClrProfiler.Managed) type:bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Application fails to start with FileNotFoundException (Could not load file or assembly ‘System.Net.Http, Version=4.0.0.0’ . . .)

3 participants