Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent clients from overriding your service in C# using a public interface?

We develop a NuGet package that customer can use add to their ASP.NET site.

public interface IOurService {
  void DoStuff();
}

public class OurService: IOurService {
  public void DoStuff() {
    //do things here
  }
}

Then we have a method to register our services in DI

public static void AddOurServices(this IServiceCollection services) {
  services.AddSingleton<IOurService, OurService>();
}

The reason we have the interface IOurService is that customer wants to mock the service in their unit tests, if the service is locked down, they are saying that testing is difficult.

However, the problem is that they can override IOurService with their own implementation through DI.

services.AddOurSerivces();
services.AddSingleton<IOurService, TheirImplementation>();

Since we need the interface so the service can be mocked, we need to keep the interface and make it public. However, is there any practice to prevent clients from replacing our implementation through DI? If they do so and it does not work or introduce bugs, they will create support cases and we have to deal with that. It would be nice if we can prevent them from breaking the library in the first place.

like image 710
stormtrooper Avatar asked Sep 16 '25 23:09

stormtrooper


1 Answers

You might try whether:

public abstract class OurService
{
  internal OurService() {}
  // polymorphic methods here
}

internal sealed class OurServiceImpl : OurService
{
  // overrides here
}

does what you want, where OurService replaces the interface and OurServiceImpl is the type added by your registration code. Most mocking frameworks are perfectly capable of working past accessibility rules (the internal constructor), but this would be inconvenient to most regular code. OurServiceImpl would either be in the same assembly, or in an assembly nominated via [InternalsVisibleTo].

Important: "inconvenient" is not the same as "impossible". A sufficiently motivated actor can still bypass this via ref-emit.

like image 151
Marc Gravell Avatar answered Sep 19 '25 15:09

Marc Gravell