InternalsVisibleTo attribute isn't working

Viewed 61154

I am trying to use the InternalsVisibleTo assembly attribute to make my internal classes in a .NET class library visible to my unit test project. For some reason, I keep getting an error message that says:

'MyClassName' is inaccessible due to its protection level

Both assemblies are signed and I have the correct key listed in the attribute declaration. Any ideas?

21 Answers

Are you absolutely sure you have the correct public key specified in the attribute? Note that you need to specify the full public key, not just the public key token. It looks something like:

[assembly: InternalsVisibleTo("MyFriendAssembly,
PublicKey=0024000004800000940000000602000000240000525341310004000001000100F73
F4DDC11F0CA6209BC63EFCBBAC3DACB04B612E04FA07F01D919FB5A1579D20283DC12901C8B66
A08FB8A9CB6A5E81989007B3AA43CD7442BED6D21F4D33FB590A46420FB75265C889D536A9519
674440C3C2FB06C5924360243CACD4B641BE574C31A434CE845323395842FAAF106B234C2C140
6E2F553073FF557D2DB6C5")]

It's 320 or so hex digits. Not sure why you need to specify the full public key - possibly with just the public key token that is used in other assembly references it would be easier for someone to spoof the friend assembly's identity.

It is worth noting that if the "friend" (tests) assembly is written in C++/CLI rather than C#/VB.NET, you need to use the following:

#using "AssemblyUnderTest.dll" as_friend

instead of a project reference or the usual #using statement. For some reason, there is no way to do this in the project reference UI.

Here's a macro I use to quickly generate this attribute. Its a bit hacky, but it works. On my machine. When the latest signed binary is in /bin/debug. Etc equivocation etc. Anyhow, you can see how it gets the key, so that'll give you a hint. Fix/improve as your time permits.

Sub GetInternalsVisibleToForCurrentProject()
    Dim temp = "[assembly:  global::System.Runtime.CompilerServices." + _
               "InternalsVisibleTo(""{0}, publickey={1}"")]"
    Dim projs As System.Array
    Dim proj As Project
    projs = DTE.ActiveSolutionProjects()
    If projs.Length < 1 Then
        Return
    End If

    proj = CType(projs.GetValue(0), EnvDTE.Project)
    Dim path, dir, filename As String
    path = proj.FullName
    dir = System.IO.Path.GetDirectoryName(path)
    filename = System.IO.Path.GetFileNameWithoutExtension(path)
    filename = System.IO.Path.ChangeExtension(filename, "dll")
    dir += "\bin\debug\"
    filename = System.IO.Path.Combine(dir, filename)
    If Not System.IO.File.Exists(filename) Then
        MsgBox("Cannot load file " + filename)
        Return
    End If
    Dim assy As System.Reflection.Assembly
    assy = System.Reflection.Assembly.Load(filename)
    Dim pk As Byte() = assy.GetName().GetPublicKey()
    Dim hex As String = BitConverter.ToString(pk).Replace("-", "")
    System.Windows.Forms.Clipboard.SetText(String.Format(temp, assy.GetName().Name, hex))
    MsgBox("InternalsVisibleTo attribute copied to the clipboard.")
End Sub

You need to use the /out: compiler switch when compiling the friend assembly (the assembly that does not contain the InternalsVisibleTo attribute).

The compiler needs to know the name of the assembly being compiled in order to determine if the resulting assembly should be considered a friend assembly.

Another possibility that may be tricky to track down, depending on how your code is written.

  1. You're invoking an internal method defined in X from another assembly Y
  2. The method signature uses internal types defined in Z
  3. You then have to add [InternalsVisibleTo] in X AND in Z

For example:

// In X
internal static class XType
{
    internal static ZType GetZ() { ... }
}

// In Y:
object someUntypedValue = XType.GetZ();

// In Z:
internal class ZType { ... }

If you have it written like above, where you're not referring to ZType directly in Y, after having added Y as a friend of X, you may be mystified why your code still doesn't compile.

The compilation error could definitely be more helpful in this case.

I'm writing this out of frustration. Make sure the assembly you are granting access to is named as you expect.

I renamed my project but this does not automatically update the Assembly Name. Right click your project and click Properties. Under Application, ensure that the Assembly Name and Default Namespace are what you expect.

1- Sign the test project: In Visual Studio go to the properties window of the test project and Sign the assembly by checking the checkbox with the same phrase in the Signing tab.

2- Create a PublicKey for the test project: Open Visual Studio Command Prompt (e.g. Developer Command Prompt for VS 2017). Go to the folder where the .dll file of the test project exists. Create a Public Key via sn.exe:

sn -Tp TestProject.dll

Note that the argument is -Tp, but not -tp.

3- Introduce the PublicKey to the project to be tested: Go to the AssemblyInfo.cs file in the project to be tested and add this line with the PublicKey created in the previous step:

[assembly: InternalsVisibleTo("TestProjectAssemblyName, PublicKey=2066212d128683a85f31645c60719617ba512c0bfdba6791612ed56350368f6cc40a17b4942ff16cda9e760684658fa3f357c137a1005b04cb002400000480000094000000060200000024000052534131000400000100010065fe67a14eb30ffcdd99880e9d725f04e5c720dffc561b23e2953c34db8b7c5d4643f476408ad1b1e28d6bde7d64279b0f51bf0e60be2d383a6c497bf27307447506b746bd2075")]

Don't forget to replace the above PublicKey with yours.

4- Make the private method internal: In the project to be tested change the access modifier of the method to internal.

internal static void DoSomething(){...}

Although using an AssemblyInfo file and adding InternalsVisibleTo still works, the preferred approach now (as far as I can tell) is to use an ItemGroup in your Project's csproj file, like so:

    <ItemGroup>
        <InternalsVisibleTo Include="My.Project.Tests" />
    </ItemGroup>

If a PublicKey is required, this attribute may also be added.

InternalsVisibleTo is often used in the context of testing. If you're using a mocking framework such as Moq, you will need to expose the internals of your project to Moq as well. In the particular case of that framework, I also needed to add
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]to my AssemblyInfo.

Related