No registration or configuration is needed, because the types are hard coded. If you need mocking support, a single IsTest config setting could be used in a conditional statement to determine which implementation to return.
That's very little additional coding if you use a ternary statement, and it lets you opt-in to the mocking mechanism if/when/where you need it, instead of permiating your entire architecture with it.
What you do instead is just:
public static IMyService myService;
public static IMyService getMyService() { if myService return myService; else if(isInTest()) return mockedMyService; else return new MyProdService(wtv); }
And call that in your controller that needs to use MyService.
This is different in that it's static. You don't add a service to it, it creates it the first time you ask for it (if singleton), or everytime you ask for it (otherwise), or pools it, etc.
I recon some similarities, but most enterprise "service locator" involve a lot more than this, and often are meant to give you this runtime dynamism where you can load and unload services as the app is running, etc. making them way more complex for apps that don't care about this.
var service = Startup.getMyService();
And potentially know if the controller should dispose service.
This doesn't seem much less "in the way" or "massive" than:
services.AddSingleton<IMyService,MyService>();