When building modular applications using an enterprise architecture, like the one from my colleague Ben Abt at AspNetCoreEnterprisePlatform, you may want certain commands or operations to stay hidden while still providing users access to higher-level functionality. This is where the InternalsVisibleTo attribute comes in handy. It allows designated assemblies (think: “friends of the family”) to access internal commands while keeping them hidden from the rest. The naming here aligns with Ben’s architecture, but for your project, it could differ slightly—though it’s essentially using a Mediator pattern under the hood.
Why Use InternalsVisibleTo?#
In our example, we have a project structure like this:
- Providers.MyProvider — contains the core functionality and command handlers.
- Providers.MyProvider.DependencyInjection — handles DI registrations.
- Providers.MyProvider.Engine.Commands — contains command records (like
TurnLightOnCommand). - Providers.MyProvider.Engine.Queries — holds query records for retrieving information.
Now, imagine we’re controlling a light with commands. Users of our provider shouldn’t need to worry about low-level details, like when to turn a light off before turning it on. Instead, we want them to use a higher-level command like AdjustLightingCommand, which handles both the TurnLightOnCommand and TurnLightOffCommand internally.
However, to keep things clean and prevent misuse, we don’t want users to call TurnLightOnCommand directly. To do this, we’ll make it an internal class in Providers.MyProvider.Engine.Commands. But we also need it accessible within our provider’s DependencyInjection project so it can be registered properly.
This is where InternalsVisibleTo comes into play.
Setting Up InternalsVisibleTo#
In Providers.MyProvider.Engine.Commands (where our internal command records are defined) and Providers.MyProvider (where our handlers live), add an AssemblyInfo.cs file if one doesn’t already exist.
In Providers.MyProvider.Engine.Commands/AssemblyInfo.cs, add these attributes:
| |
In Providers.MyProvider/AssemblyInfo.cs, add this attribute:
| |
Add the attribute above the namespace in AssemblyInfo.cs to ensure it applies to the entire namespace, making internals available to the specified projects across the whole assembly.
With this setup, any commands marked as internal in Providers.MyProvider.Engine.Commands will be visible to Providers.MyProvider and Providers.MyProvider.DependencyInjection, while internal classes in Providers.MyProvider will only be accessible by Providers.MyProvider.DependencyInjection. This keeps the internals contained while allowing controlled access where needed.
Example: Abstracting Lighting Control#
Here’s a simple setup where a user might need to interact with a lighting provider but shouldn’t directly call TurnLightOnCommand or TurnLightOffCommand:
Step 1: Define the Commands#
In Providers.MyProvider.Engine.Commands, we define our internal commands:
| |
Step 2: Create the Handler#
In Providers.MyProvider, we create handlers for these commands that control the light’s behavior:
| |
Step 3: Add a Public AdjustLightingCommand#
Instead of exposing the individual on/off commands, we provide a high-level AdjustLightingCommand for users. This command handles any necessary internal actions, ensuring users don’t have to worry about the details.
| |
Step 4: Register in Dependency Injection#
In Providers.MyProvider.DependencyInjection, you can use InternalsVisibleTo to reference and register the internal commands in the DI container:
| |
Wrapping Up#
With InternalsVisibleTo, you get the best of both worlds: internal commands stay hidden from users who don’t need them, while the provider can still access and register them internally. This approach keeps your API clean and prevents misuse while still giving users easy access to all the functionality they need.
This setup is perfect for scenarios where commands are complex and interdependent, providing users with a well-abstracted, user-friendly interface without exposing the nuts and bolts underneath.
