How are references located in .NET?
Asked Answered
S

1

10

What process does .NET use to locate a referenced assembly at runtime and is it different than the process used to locate a referenced assembly during compilation? I am specifically interested in the locations that are searched and their search order and any parameters/configuration settings that might affect the outcome.

Scutate answered 1/9, 2015 at 22:51 Comment(2)
Could you be more specific? YOu can see the path of a references in: solution > project > references > "desired reference" > properties.Dougall
Question reworded to be more specific.Scutate
S
15

There are three types of references that can be made in a .NET application. This answer will only cover the first two in the list below.

  1. File references.
  2. Project references
  3. Service references.

Every reference must be resolved. Reference resolution is the process of locating a concrete instance of a reference in the form of a file. Project references are resolved in the same way that file references are resolved. A project reference just allows you to reference assemblies that do not yet exist (because it is an output of the build process.)

It is important to understand that reference resolution occurs at both compile time and at runtime and the process for each is totally different. Failing to understand this point can lead to endless headache. Believe me, I know.

Runtime Reference Resolution (aka binding)

When an application in invoked, it must be loaded into memory. If an application uses objects in another assembly, that assembly must also be loaded into memory. The .NET framework uses the following process to do this.

  1. Determine version of referenced assembly.
    • The version of the referenced assembly is written to the applications manifest at compile time. This version will be used unless overridden in configuration.
      • application/web.config
      • Publish Policy (overrides application/web.config)
      • machine.config (overrides Publish Policy and application/web.config)
  2. If assembly was previously loaded, then re-use from cache.
  3. If strong-name provided, search GAC.
  4. Probe
    • If codebase element specified, then use.
      • Binding failure if not found.
      • Binding failure if version, culture, or public key mismatch.
    • Search application base path. Matches by simple name and fails if first match is wrong version.
      • If no culture provided, search root then root/[assembly name]
      • If culture provided, search root/[culture] then root/[culture]/[assembly name].
      • If web/app.config specifies probing element, search paths in privatePath. Paths must be relative to application root.

For more information see http://msdn.microsoft.com/en-us/library/yx7xezcf%28v=vs.110%29.aspx.

Compile Time Reference Resolution

Compile time resolution occurs in MSBuild during the build process. MSBuild is the build engine used by both Visual Studio and TFS. Note that for ASP.NET applications, there is an extra compile step that occurs for dynamic components (aspx, asc, asax, cshtml, etc) when they are first accessed. Reference resolution for these two scenarios is described below.

MSBuild

Assembly resolution occurs in the ResolveAssemblyReferences MSBuild target. This target invokes the ResolveAssemblyReference task passing the value of the AssemblySearchPaths to the SearchPaths parameter which is assigned a value as follows.

<PropertyGroup>
        <!--
        The SearchPaths property is set to find assemblies in the following order:

            (1) Files from current project - indicated by {CandidateAssemblyFiles}
            (2) $(ReferencePath) - the reference path property, which comes from the .USER file.
            (3) The hintpath from the referenced item itself, indicated by {HintPathFromItem}.
            (4) The directory of MSBuild's "target" runtime from GetFrameworkPath.
                The "target" runtime folder is the folder of the runtime that MSBuild is a part of.
            (5) Registered assembly folders, indicated by {Registry:*,*,*}
            (6) Legacy registered assembly folders, indicated by {AssemblyFolders}
            (7) Resolve to the GAC.
            (8) Treat the reference's Include as if it were a real file name.
            (9) Look in the application's output folder (like bin\debug)
        -->
    <AssemblySearchPaths Condition=" '$(AssemblySearchPaths)' == ''">
      {CandidateAssemblyFiles};
      $(ReferencePath);
      {HintPathFromItem};
      {TargetFrameworkDirectory};
      {Registry:$(FrameworkRegistryBase),$(TargetFrameworkVersion),$(AssemblyFoldersSuffix)$(AssemblyFoldersExConditions)};
      {AssemblyFolders};
      {GAC};
      {RawFileName};
      $(OutDir)
    </AssemblySearchPaths>

There is a lot going on here and I don't claim to understand all of it, but I will try to point out the important parts.

  1. The most common locations to find a reference are (in search order)
    • Files added manually to project (ex. /lib/coollib.dll>
    • Location specified by hint path.
    • GAC
    • Application output path.
  2. References flagged with Copy Local = true are copied to the application output path after compilation. This means that the value of this setting has no impact in the reference resolution process for MSBuild. Note that the Copy Local UI setting maps to the <private> element in the project file.
  3. MSBuild will always try to use the latest version available for a given assembly, unless specific version = true is specified. The default value for this setting is false which means that when searching the GAC, the latest version of a DLL will always be used regardless of the version specified in the project definition.

ASP.NET Runtime Compiler

Unless previously compiled into the project output folder using the pre-compile option at build time, all dynamic content (aspx, asc, asax, cshtml, etc.) is compiled once at runtime when the application is first accessed. This dynamic content can also have dependencies on other assemblies. The system.web > compilation > assemblies element is used to tell the ASP.NET runtime compiler about these dependencies so that it can reference them.

The ASP.NET runtime compiler will search the following locations in order for these references.

  1. The applications private assembly cache (aka PAC) which is the /bin folder.
  2. GAC (if reference is specified using strong name).

Note that by default, the root web.config references a few system assemblies and all assemblies in the PAC using the wildcard syntax. This means that you will rarely ever be required to explicitly add references manually to the system.web > compilation > assemblies element. In many cases you can and should delete the element entirely. It should only contain references to assemblies stored in the GAC. Using Copy Local = true is the recommended approach to include non-GAC references required by the ASP.NET Runtime Compiler.

Also note that many subtle errors can occur if you use the system.web > compilation > assemblies element to specify a specific version number using the assembly's strong name. The ASP.NET runtime compiler will attempt to compile using the exact version you specify. This can cause problems if the non-dynamic components of the application were compiled against a different version of the assembly during the MSBuild compilation phase. This is often the case because MSBuild will use the latest version it can find and only the exact version if you set specific version = true.

Additional Resources:

http://jack.ukleja.com/diagnosing-asp-net-page-compilation-errors/ http://blog.fredrikhaglund.se/blog/2008/02/23/get-control-over-your-assembly-dependencies/ https://dhakshinamoorthy.wordpress.com/2011/10/01/msbuild-assembly-resolve-order/ http://www.beefycode.com/post/resolving-binary-references-in-msbuild.aspx

Scutate answered 1/9, 2015 at 23:6 Comment(1)
FYI, from experience, just because an assembly is referenced at compile time, and just because code in your app was accessing code in that assembly, doesn't mean the assembly is loaded at runtime. The code accessing code in that assembly needs to be called by the application. If the code is not called at runtime, then you need to use System.Reflection.Assembly.Load(“SomeAssembly”) to load the assembly. I blogged about this here: medium.com/@DavidKlempfner/…Sibby

© 2022 - 2024 — McMap. All rights reserved.