Check a .NET DLL's Target Framework, Version and References
On this page
Short answer: a .NET DLL records the framework it was built for in an assembly-level TargetFrameworkAttribute, with a value such as .NETCoreApp,Version=v6.0, .NETFramework,Version=v4.5 or .NETStandard,Version=v2.0. Its assembly version sits in the metadata Assembly table, and every assembly it depends on is an AssemblyRef row. You can read all three without Visual Studio, without the .NET SDK and without loading the DLL: drop it into this site’s .NET metadata and IL viewer, or run rea inspect-managed-artifact from REA, the open-source project by morluto.
Two cases trip people up, and this guide covers both with real output: DLLs built for .NET Framework 2.0 or 3.5 carry no target framework attribute at all, and the version you see in NuGet is usually not the assembly version.
Who this is for: developers chasing a FileLoadException or a binding redirect, planning a migration from .NET Framework, or checking what a NuGet package actually ships before they depend on it.
What you will answer:
- Is this file a .NET assembly at all, or a native DLL?
- Which framework does it target, even when the attribute is missing?
- What are its assembly version, file version and product version?
- Which assemblies does it reference, and in which versions?
- What does the IL of one method look like (an ildasm alternative)?
Sample used: Newtonsoft.Json 13.0.3 from nuget.org (MIT license). Its package contains eight builds of the same library, one per target, which makes it ideal for comparison:
curl -sSL -o nj.nupkg https://api.nuget.org/v3-flatcontainer/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg
unzip -q nj.nupkg -d nj
ls nj/lib
# net20 net35 net40 net45 net6.0 netstandard1.0 netstandard1.3 netstandard2.0
Use the same steps on your own build output or on packages you depend on. If you later move on to running or debugging someone else’s application, make sure you are authorized to, and read REA’s DISCLAIMER.md.
Step 1: Is it a .NET assembly or a native DLL?
A .dll extension says nothing about the contents. A .NET assembly is a PE file whose data directory 14 points to a CLI header, which in turn points to the metadata. A native DLL has no CLI header.
The .NET viewer checks this first. For a native PE it stops with “This is a native PE, not a .NET assembly (no CLI header).” If it finds NativeAOT markers (a .managed or hydrated section, or the string DotNetRuntimeDebugHeader), it says the file is NativeAOT output, which contains machine code and no IL metadata.
REA reports the same distinction as data. I ran 6.4.0’s inspector (dist/dotnet/ManagedArtifactInspector.js) on a native PE produced by this site’s own test builder, and it returned this classification (some fields trimmed):
{
"status": "not-managed",
"container": "pe",
"implementation": "not-managed",
"evidence": [{ "code": "cli-directory-absent",
"detail": "The PE has no CLI data directory; managed metadata and CIL are unavailable." }]
}
If you get this result, none of the steps below apply. A native DLL needs a native tool; see Windows EXE and DLL analysis with REA.
Step 2: Read the target framework
Open nj/lib/net6.0/Newtonsoft.Json.dll in the viewer. The assembly identity block shows the target framework from TargetFrameworkAttribute. Here is what the viewer and REA 6.4.0 reported for all eight builds:
| Folder | Target framework (attribute) | Metadata version | REA runtime_family |
|---|---|---|---|
net20 |
(none) | v2.0.50727 |
unknown |
net35 |
(none) | v2.0.50727 |
unknown |
net40 |
.NETFramework,Version=v4.0 |
v4.0.30319 |
dotnet-framework |
net45 |
.NETFramework,Version=v4.5 |
v4.0.30319 |
dotnet-framework |
net6.0 |
.NETCoreApp,Version=v6.0 |
v4.0.30319 |
modern-dotnet |
netstandard1.0 |
.NETStandard,Version=v1.0 |
v4.0.30319 |
modern-dotnet |
netstandard1.3 |
.NETStandard,Version=v1.3 |
v4.0.30319 |
modern-dotnet |
netstandard2.0 |
.NETStandard,Version=v2.0 |
v4.0.30319 |
modern-dotnet |
The viewer and REA agree on every value. That is expected, since the viewer’s attribute decoding is ported from REA 6.4.0 (src/tools/dotnet/analyze.ts credits ManagedMetadataInventoryRows.ts).
When the attribute is missing
System.Runtime.Versioning.TargetFrameworkAttribute arrived with .NET Framework 4.0, so compilers targeting 2.0 or 3.5 do not emit it. For those builds, use the other evidence in the same report:
- Metadata version
v2.0.50727means the CLR 2 runtime, which .NET Framework 2.0, 3.0 and 3.5 share.v4.0.30319is written by everything from .NET Framework 4.0 on, including .NET Core and .NET 5+, so on its own it cannot separate .NET Framework 4.x from .NET 6. - References settle the rest. The
net20build referencesmscorlib 2.0.0.0,System 2.0.0.0,System.Xml 2.0.0.0andSystem.Data 2.0.0.0. Thenet35build addsSystem.Core 3.5.0.0andSystem.Xml.Linq 3.5.0.0, assemblies that first shipped with 3.5.
REA is careful here: for both builds it returns runtime_family: "unknown" and an empty target_frameworks list rather than guessing.
Why .NET Standard shows as modern-dotnet
In 6.4.0, REA’s runtimeFamilyFor treats a .NETCoreApp, or .NETStandard, attribute as evidence for modern-dotnet. Keep in mind that .NET Standard is an API contract, not a runtime: a netstandard2.0 library also runs on .NET Framework 4.6.1 and later. The target_frameworks value is the precise fact, and runtime_family is a classification on top of it.
Step 3: Assembly version, file version and product version
All eight DLLs in the 13.0.3 package report the same identity:
Newtonsoft.Json 13.0.0.0, public key token 30ad4fe6b2a6aeed
13.0.0.0 is the assembly version, the one the runtime compares when it binds a strong-named reference and the one binding redirects are written against. The NuGet package version (13.0.3) is a different number.
The file version and product version live elsewhere: in the Win32 version resource, mirrored by the AssemblyFileVersionAttribute and AssemblyInformationalVersionAttribute. The browser viewer does not display them, and REA 6.4.0 lists those attributes but decodes the string argument of TargetFrameworkAttribute only. Every other attribute’s decoded_fixed_string is null, with a value_sha256 of the raw blob (ManagedMetadataInventoryRows.js).
On Windows, PowerShell reads the version resource directly:
(Get-Item .\Newtonsoft.Json.dll).VersionInfo | Format-List FileVersion, ProductVersion
[System.Reflection.AssemblyName]::GetAssemblyName("$PWD\Newtonsoft.Json.dll").Version
On macOS or Linux, the version resource strings are UTF-16 and easy to find. This short script is what I used to check the sample:
import re, sys
data = open(sys.argv[1], "rb").read()
for key in ("FileVersion", "ProductVersion", "Assembly Version"):
m = re.search(re.escape(key.encode("utf-16-le")) + rb"\x00\x00+((?:[^\x00]\x00)+)", data)
print(f"{key}: {m.group(1).decode('utf-16-le') if m else '(not found)'}")
FileVersion: 13.0.3.27908
ProductVersion: 13.0.3+0a2e291c0d9c0c7675d445703e51750363a549ef
Assembly Version: 13.0.0.0
So the three numbers are 13.0.0.0 (assembly), 13.0.3.27908 (file) and 13.0.3 plus a commit hash (product). When someone asks “which version is this DLL?”, ask which one they mean.
Step 4: List the references and spot conflicts
The viewer’s References list is the assembly’s AssemblyRef table: name, version, culture and public key token. Comparing three builds of the same library shows how much the target changes the dependency surface:
net45: 8 references, all4.0.0.0(mscorlib,System,System.Core,System.Numerics,System.Xml,System.Xml.Linq,System.Runtime.Serialization,System.Data).net6.0: 22 references, all6.0.0.0, starting withSystem.Runtime,System.Runtime.InteropServicesandSystem.Runtime.Numerics. There is nomscorlib, which is itself a strong sign of .NET Core or .NET 5+.netstandard2.0: exactly one reference,netstandard 2.0.0.0.
How this helps with version conflicts:
- Note the exact version of each referenced assembly. A reference is a request for that assembly version (or, with binding redirects on .NET Framework, a compatible one).
- Open the DLL that actually sits next to your app and compare its assembly version. A reference to
Foo 2.0.0.0with onlyFoo 1.5.0.0deployed is the classicFileLoadException. - A higher reference version in a dependency than in your app is the usual reason a binding redirect is needed on .NET Framework.
The viewer also lists ModuleRef rows and P/Invoke declarations. They are the native DLLs the assembly calls, which matters if you are porting it to another OS.
Step 5: Read a method’s IL (an ildasm alternative)
The type tree lists each method with a C#-style signature and its metadata token. In the net6.0 build, Newtonsoft.Json.JsonConvert has string SerializeObject(object value) with token 0x06000055. Clicking it disassembles the body (shown here as plain text):
tiny header, maxStack 8, codeSize 9
IL_0000 ldarg.0
IL_0001 ldnull
IL_0002 ldnull
IL_0003 call string Newtonsoft.Json.JsonConvert::SerializeObject(object, System.Type, Newtonsoft.Json.JsonSerializerSettings)
IL_0008 ret
That is the one-argument overload forwarding to the three-argument overload with null for the type and settings, which matches the public source. Tokens are build-local: 0x06000055 refers to this method only in this exact file, so record the file’s SHA-256 next to any token you write down.
The viewer shows metadata and IL. It does not decompile to C#. For C#, use ILSpy or another decompiler locally on assemblies you are allowed to examine.
Step 6: The same from the command line with REA
For scripts, CI or an AI agent, use REA 6.4.0. The CLI takes one argument, the path (dist/cli/managedCommands.js):
DLL=/absolute/path/nj/lib/net6.0/Newtonsoft.Json.dll
rea inspect-managed-artifact "$DLL" --json > artifact.json
jq '.normalized_result.classification | {status, runtime_family, implementation, managed_architecture}' artifact.json
jq '.normalized_result | {target_frameworks, name: .assembly.name, version: .assembly.version}' artifact.json
jq -r '.normalized_result.references[] | "\(.name) \(.version)"' artifact.json
For the net6.0 build, 6.4.0’s inspector produced status: "managed", runtime_family: "modern-dotnet", implementation: "cil", managed_architecture: "anycpu", target_frameworks: [".NETCoreApp,Version=v6.0"], assembly Newtonsoft.Json 13.0.0.0, and the same 22 references as the viewer. Each classification comes with evidence codes and file offsets, here cli-header-observed, target-framework-observed and managed-architecture-derived. The result’s limitation line is explicit: “Static inspection did not load the assembly, resolve dependencies through a CLR, decompile C#, or execute target code.”
Note anycpu. The viewer’s PE header block shows PE32 and machine x86 for this file, because AnyCPU assemblies use a 32-bit PE header. REA derives anycpu from the CLI flags: ILONLY is set and 32BITREQUIRED is not. Read the CLI flags, not the PE machine, when you want to know whether an assembly runs as 64-bit.
For members, signatures and CIL hashes across the whole assembly, follow with rea inspect-managed-members "$DLL", covered in .NET Assembly Static Analysis with REA. In 6.4.0 that command also takes only the path.
Compared with the usual tools
- PowerShell
AssemblyName.GetAssemblyNamegives name, version and public key token, andVersionInfogives file and product version. It does not list the target framework or the references in one call. I have only used these commands on Windows. - ildasm ships with the Windows SDK and shows the manifest (references, attributes) and IL. It is Windows-only.
- ILSpy shows all of the above plus decompiled C#, and is the right tool when you need source-level understanding.
- The browser viewer and REA need nothing installed (or only Node.js for REA), work on any OS, never load the assembly, and REA’s JSON output is easy to diff or feed to an agent.
FAQ
Is my DLL safe to open in the viewer? The file is parsed in a Web Worker in your browser and never uploaded or executed. The worker’s Content Security Policy forbids network requests.
The viewer shows no target framework. Is the file broken?
Probably not. Builds for .NET Framework 2.0 to 3.5 have no TargetFrameworkAttribute. Use the metadata version and references, as in Step 2.
Why does every Newtonsoft.Json 13.x DLL say 13.0.0.0? I only checked 13.0.3, where all eight builds report 13.0.0.0 while the file version is 13.0.3.27908. Keeping the assembly version fixed within a major release means apps do not need new binding redirects for every patch.
Can I check a NativeAOT or single-file app? A NativeAOT binary has no IL metadata, and the viewer says so. A single-file bundle starts with a native host; extract the bundled assemblies first and inspect those.
Next steps
- Inspect your own assemblies: .NET metadata and IL viewer
- Read members, CIL and version-to-version changes: .NET Assembly Static Analysis with REA
- Compare two builds of an artifact: Evidence, snapshots and version comparison
- Reference: TargetFrameworkAttribute, Assembly versioning, ECMA-335