Alex Q
Alex Q

Reputation: 125

Inject Dependency With Internal Methods

I want to define an injectable dependency in a "platform" assembly. I want consuming assemblies to inject the dependency, but I don't want them to be able to use it directly. In other words, I want the dependency itself to be public, but the dependency's methods to be internal. Obviously you can't define define a public interface with internal methods.

I have come up with a solution, but I'd like to know if there's a cleaner approach. Basically, rather than define an interface I take a C++ inspired approach and define a pure abstract class. The whole thing would look something like this:

In "platform" assembly.

public abstract class IDependency
{
     public abstract void DoSomethingPublic();
     internal abstract void DoSomethingInternal();
}

public class DependencyImplementation : IDependency
{
     public override void DoSomethingPublic()
     {
          ...
     }

     public override void DoSomethingInternal()
     {
          ...
     }
}

public class Consumer
{
     public void Consume(IDependency dependency)
     {
          ...

          dependency.DoSomethingInternal();

          ...
     }
}

In consuming assembly:

var consumer = new Consumer();
var dependency = new DependencyImplementation();
consumer.Consume(dependency);

This does everything I want, but it just seems like a hacky solution. Is there another solution to this problem, or is this an acceptable solution?


Edit: Here's a more concrete example with my actual problem. I have a platform assembly and a website assembly. In my platform assembly I have the following extension method:

public static bool HasSettingEnabled(this IUser user, ISettingFactory factory)
{
     var settingEntity = factory.GetOrCreateByUserId(user.Id);
     return settingEntity.IsEnabled || user.ShouldForciblyEnableSetting;
}

In my website assembly I could check if the setting is enabled by either calling user.HasSettingEnabled() or getting the setting from the factory directly. However, I want to restrict the website assembly from checking the setting through the factory directly because the logic around forcibly enabling the setting would be missed. So I want my ISettingFactory to be injectable from the website, but I don't want to be able to call ISettingFactory.GetOrCreateByUserId() outside of the platform. Using the pattern I have above you get the following:

Platform assembly:

public abstract class ISettingFactory
{
     internal abstract Setting GetOrCreateByUserId(int userId);
}

public class SettingFactory : ISettingFactory
{
     internal override Setting GetOrCreateByUserId(int userId)
     {
          // Get setting entity from DB
          ...
          return setting;
     }
}

public static class IUserExtension
{
    public static bool HasSettingEnabled(this IUser user, ISettingFactory factory)
    {
         var settingEntity = factory.GetOrCreateByUserId(user.Id);
         return settingEntity.IsEnabled || user.ShouldForciblyEnableSetting;
    }
}

And in the website assembly:

var settingFactory = new SettingFactory();
if (user.HasSettingEnabled(settingFactory))
{
     // Do something.
     ...
}

So you can see that the website only ever injects the factory and never can the get or create method directly. This guarantees that there is only one code path the get the setting for the user.

Upvotes: 1

Views: 1511

Answers (1)

Michael Stum
Michael Stum

Reputation: 180934

I'm not sure I 100% understand what you want - is the implementation of the interface provided by the consumer? Or does the Platform assembly include all implementations of the IDependency? In the latter case, maybe an internal interface can help.

A public class can implement an internal interface. So you could put all your internal methods in an internal interface and all public methods in a public one and only expose the public. That way in your platform assembly you can use IInternalDependency but publicly only expose IDependency.

public interface IDependency
{
     void DoSomethingPublic();
}

internal interface IInternalDependency : IDependency
{
    void DoSomethingInternal();
}

public class YourClass: IInternalDependency
{
   public void DoSomethingPublic() 
   {
   }

   // Note the Explicit interface member implementation
   void IInternalDependency.DoSomethingInternal()
   {
   }
}

In your Consumer code, you'd have to try to cast it:

 public void Consume(IDependency dependency)
 {    
      var intDependency = dependency as IInternalDependency;
      if(intDependency == null)
      {
         throw new InvalidOperationException();
      }
      intDependency.DoSomethingInternal();
 }

Upvotes: 1

Related Questions