maybe someone can help me out with this one:
I'm writing a Program, that dynamically loads assemblies that have different implementations of the same interface "IMyService".
The case may occur, that some of the assemblies aren't even there (imagine this as a set of different modules of a software a user can buy... Some are bought, some aint, therefore the functionality isn't available and the dll isn't delivered).
So what I'm trying to do is the following:
private IServiceProvider ConfigureServices()
{
const string vendor = "MyVendor";
var assembly = Assembly.LoadFrom($"{vendor}.dll");
var myVendorType = assembly.GetType($"{vendor}.Services.{vendor}Service");
if (myVendorType == null)
throw new Exception($"Module '{vendor}' not found");
var services = new ServiceCollection();
// ... other services
serviceCollection.TryAddSingleton<IMyService, myVendorType>();
return serviceCollection.BuildServiceProvider();
}
Unfortunately this won't compile, since the IDE is telling me that it can't resolve the Symbol "myVendorType", when the Type is provided as the Implementation of the "TryAddSingleton..."
I know that the creation of an instance needs an Activator like so:
Activator.CreateInstance(myVendorType);
but, I have no idea what to do, when I want to provide the type to implement to the Service-Collection.
I hope someone has an idea :)
As I mentioned in the comment you could register instance directly in service collection.
For that you could do services.TryAddSingleton<IMyService>(sp => (IMyService)Activator.CreateInstance(myVendorType))
Or even just services.TryAddSingleton<IMyService>( (IMyService)Activator.CreateInstance(myVendorType))
The first option is useful when you need to get something other from ServiceProvider to correctly instantiate the needed instance
Related
We're using ASP.NET core and are running into the problem, that some registered services (from third-party libraries) request a specific 'service' (based on an interface) that has been deprecated.
The problem is that we don't know which libraries are using this deprecated service interface.
What we've done so far is:
create a custom implementation for the service interface
and registered this custom implementation (class) with DI
// Registration
services.AddTransient<IServiceInterface>((services) => new CustomCustomService(Log.Logger));
// Custom implementation
internal class CustomService : IServiceInterface
{
public CustomService (ILogger logger)
{
logger.Warning("!!! CustomService is still being used !!!");
}
}
So now we can see that the unwanted service is being used 'somewhere'.
But is it in some way possible to detect for which service the deprecated service has been created?
I've tried listing the stack trace using
var st = new System.Diagnostics.StackTrace();
logger.Warning("!!! CustomService is still being used !!!" + Environment.NewLine + "{stacktrace}", st.ToString());
But that doesn't seem to give information about the service using the deprecated service...
You can try the following:
var registrationsDependingOnMyService =
from descriptor in services
where descriptor.ImplementationType != null
let dependencies =
from ctor in descriptor.ImplementationType!.GetConstructors()
from param in ctor.GetParameters()
select param.ParameterType
where dependencies.Contains(typeof(IServiceInterface))
select descriptor;
This will query the IServiceCollection for registrations whose implementation type have a constructor argument of type IServiceInterface.
This might not be a bulletproof solution, as types or registrations can more sneakily depend on the service collection (e.g. by calling back into the IServiceProvider from within a registration delegate), but this is likely the best you can do with MS.DI.
So basically - as expected - it's not possible to exactly know which libraries (for which you don't have the code) use a certain dependency.
It's just trial and error ;)
Thanks for the ideas everyone.
I'm using the default DI Container from Asp.net Core and I have multiple IServiceProvider instances which I need to nest.
I know that the default DI Container doesn't support nesting by itself and that alternatives do, but if possible I don't want to switch.
Until now I only need to resolve types were it and its dependencies were always in the same container. In this case my self-rolled superficial nested ServiceProvider could properly resolve the types by simple first checking the child container and then the parent container if it couldn't find it in the child container.
But now I have a type that I want to resolve which is registered in the child container and has dependencies on types registered in the parent container.
In this case my self-rolled solution fails.
So I'm wondering if it is possible to provide a callback which is called when the ServiceProvider is not able to resolve one of the dependencies.
The callback would then resolve the type and return an instance of the needed type.
With this I could provide a callback which would use the parent container to resolve the needed type.
An example of what I'd like to do (Interfaces omitted for brevity):
public class A { }
public class B { public B(A a) { } }
public class ServiceProviderFallBack {
private ServiceProvider rootServiceProvider;
public ServiceProviderFallBack(ServiceProvider rootServiceProvider) => this.rootServiceProvider = rootServiceProvider;
public object Resolve(Type type) => rootServiceProvider.GetRequiredService(type);
}
public void Run() {
var rootServiceCollection = new ServiceCollection();
rootServiceCollection.AddSingleton<A>();
var rootServiceProvider = rootServiceCollection.BuildServiceProvider();
var webApiServiceCollection = new ServiceCollection();
//How could this be done, if it can be done at all?
webApiServiceCollection.AddSingleton(new ServiceProviderFallBack(rootServiceProvider));
webApiServiceCollection.AddTransient<B>();
var webApiServiceProvider = rootServiceCollection.BuildServiceProvider();
webApiServiceProvider.GetRequiredService<B>();
}
I'd like the ServiceProvider webApiServiceProvider to use the registered type ServiceProviderFallBack when it is unable to resolve a type and use the ServiceProviderFallBack.Resolve method for that instead.
The example is strongly simplified: The ServiceProviders rootServiceCollection and webApiServiceProvider are in different assemblies.
To answer the question, no it is not possible with the current implementation.
I had a look at the source code and there is no way to add a callback.
The relevant part can be found here:
DependencyInjection/src/DI/ServiceLookup/CallSiteFactory.cs
Once a type is requested GetCallSite is called which calls CreateCallSite upon the first request. And then in the case for simple registrations has the following callchain:
CreateCallSite
> TryCreateExact
> TryCreateExact
> CreateConstructorCallSite
> CreateArgumentCallSites
> GetCallSite
> CreateCallSite (If not already cached)
I have found no part which could be replaced with custom classes or where callbacks could be used.
After that didn't work out I found OrchardCore/Environment/Shell/Builders/Extensions/ServiceProviderExtensions.cs which does what I'd like to do, but I had issues with duplicate registrations.
So I abandoned the generalized approach and went with something more localized:
Instead of trying to link the two ServiceProviders I keep them separate and just add the necessary services to the "child" ServiceProvider.
I need to access a service inside ConfigureServices method in Startup.cs and I do this:
services.AddScoped<ICustomService, CustomService>();
var sp = services.BuildServiceProvider();
var service = sp.GetService<ICustomService>(); // this is null
However var service above is always null.
What do i do wrong?
I had this sort of problem - I had a singleton 'settings' service which I wanted to use. I solved it by Actually creating one then registering that exact instance with DI via the overload that lets you specify a 'provider', rather than just registering the class, and adding a nice big comment explaining this:
var settingsService = new SettingsService(_hostingEnvironment);
//Add a concrete settings service which is then registered as the de facto settings service for all time.
//we need to do this as we want to use the settings in this method, and there isn't a satisfactory method to
//pull it back out of the IServiceCollection here (we could build a provider, but then that's not the same provider
//as would be build later... at least this way I have the exact class I'll be using.
services.AddSingleton<ISettingsService, SettingsService>((p) => settingsService);
..
..
..
var thing = settingsService.SomeSettingIWant();
If what you want isn't a singleton but is something transient, then I guess you can just create a concrete class for it right there? I know it probably feels a bit like cheating, but it would work fine...
I'm using Unity within my application. For one integration test, we want to change one of the service registered to the application(because it would require some hardware).
So, I'm able to Register the new "mock" type, but I'm unable to remove the other implementation registered for this interface.
Also to mention, currently we register a "list" of this interface(it's some kind of driver) and we would like to remove all the others instance.
Any idea how I could do this?
You can simply override the registration with either mocks, or a blank implementation. In the case of named registrations, it will work as long as you register with the same name. As you add a new registration, Unity will ignore the former registrations.
In our UnitTesting projects, we currently add all the regular registrations, then in the setup we register mocks on top of them.
I'm assuming you are not using Container.IsRegistered in your tests (which is not always a valid assumption).
A similar approach to #Tipx answer is to re-register but provide an InjectionMember that will clear all of the policies associated with the type. This has the benefit that, since the policy configuration has been removed, Unity should work as if the types were never registered (even though they will appear in the Registrations list).
Here's the InjectionMember ClearAllPolicies which clears the policies:
public class ClearAllPolicies : InjectionMember
{
public override void AddPolicies(Type serviceType, Type implementationType, string name, IPolicyList policies)
{
var serviceTypeBuildKey = new NamedTypeBuildKey(serviceType, name);
policies.Clear<IBuildKeyMappingPolicy>(serviceTypeBuildKey);
var buildKey = new NamedTypeBuildKey(implementationType, name);
policies.Clear<IBuildKeyMappingPolicy>(buildKey);
policies.Clear<IConstructorSelectorPolicy>(buildKey);
policies.Clear<IBuildPlanCreatorPolicy>(buildKey);
policies.Clear<IBuildPlanPolicy>(buildKey);
policies.Clear<IMethodSelectorPolicy>(buildKey);
policies.Clear<IPropertySelectorPolicy>(buildKey);
policies.Clear<ILifetimeFactoryPolicy>(buildKey);
policies.Clear<ILifetimePolicy>(buildKey);
policies.Clear<IBuilderPolicy>(buildKey);
DependencyResolverTrackerPolicy.RemoveResolvers(policies, buildKey);
}
}
Here's an example where 3 ILogger named implementations are registered and a list of ILoggers is resolved. Then the polices are cleared for the loggers and a check is done to ensure that ILogger can't be resolved:
IUnityContainer container = new UnityContainer();
container.RegisterType<ILogger, MyLogger>("Logger1");
container.RegisterType<ILogger, MyLogger>("Logger2");
container.RegisterType<ILogger, MyLogger>("Logger3");
// Resolve list of all loggers
var loggers = container.Resolve<ILogger[]>();
// Remove Policies for all ILoggers using ClearAllPolicies
foreach (var registration in container.Registrations
.Where(r => r.RegisteredType == typeof(ILogger)))
{
container.RegisterType(
registration.RegisteredType,
registration.MappedToType,
registration.Name,
new ClearAllPolicies());
}
// Ensure that when we try to resolve an ILogger or list of ILogger's that
// an exception is thrown
Assert.Throws<ResolutionFailedException>(() => container.Resolve<ILogger>("Logger1"));
Assert.Throws<ResolutionFailedException>(() => container.Resolve<ILogger>("Logger2"));
Assert.Throws<ResolutionFailedException>(() => container.Resolve<ILogger>("Logger3"));
Assert.Throws<ResolutionFailedException>(() => container.Resolve<ILogger[]>());
Caveats & Warnings
Do not use this in production code -- I would only use this for testing purposes
It's possible that this does not capture all of the applicable policies and is not complete or does not work for every edge case (e.g. Interception or other esoteric scenarios)
Having a heck of a time figuring out how to get Autofac to load plugins. I'm new to Autofac.
I have plugins like ServiceA.dll, ServiceB.dll, etc. When configuring my Autofac container I scan the current directory for Service*.dll and pull the name of the service "A", "B", out of the filename and store in a variable named serviceName. Then I find the type of the service class that implements IService and put it in a variable named serviceType. Each service also has a config setting class named <serviceName>Settings so I find that class and put its type in a variable named serviceSettingsType. Then register:
builder.RegisterType(serviceType)
.Named<IService>(serviceName);
builder.RegisterType(serviceSettingsType)
.Named<IServiceSettings>(serviceName+"Settings")
After building the Autofac config, I can get a service type like so:
scope.ResolveNamed<IService>("A");
And presto, the class ServiceA is instantiated. However, ServiceA takes a ctor param:
public class ServiceA(ServiceASettings settings) {}
Autofac instantiates a ServiceASettings class just fine (it's just a "data class" with one or more properties) and passes that into the ctor of ServiceA.
What I can't figure out is how to tell Autofac that to create a ServiceASettings class to pass into the ServiceA ctor it needs to instantiate a SettingsManager class and then call var settings = await Task<T> ReadFromFile(string settingsFilename) (where T is the type of the ASettings class) and use that settings class in ServiceA's ctor call. Just like the service name above ("A" in the example) is determined from the command line, the settingsFilename passed to the ReadFromFileAsync needs to be passed in at runtime.
Hope this makes sense. I feel like I'm very close to loading plugins with Autofac but just can't figure out the proper factory calls or named parameter calls for the Service ctor. Anyone done something similar to this with Autofac? Suggestions for changing the architecture of my plugin classes and/or SettingsManager class are also more than welcome if it helps make this work easier with Autofac.
EDIT:
Got this to work. I re-read the page #cynic pointed to and the syntax runtime parameters finally clicked for me.
Changed my SettingsManager class to one called SettingsFile that takes a Type, an IFileSystem, and a string with the filename and exposes a ReadSettings method. Register:
builder.Register((c, p) => new SettingsFile(
c.Resolve<IFileSystem>(),
p.Named<Type>("settingsType"),
p.Named<string>("settingsFilename")))
.As<ISettingsFile>();
Resolve:
var settingsName = "Service1Settigns";
var settingsType = scope.ResolveNamed<ISettings>(settingsName);
var settingsFile = scope.Resolve<ISettingsFile>(
new NamedParameter("settingsType", settingsType.GetType()),
new NamedParameter("settingsFilename", settingsFilename));
var settings = settingsFile.ReadSettings();
(I think there's a way to get the resolver to call ReadSettings for me, but haven't figured that out yet. But I'll probably want to trap errors and enhance with very specific error messages anyhow.)
Then while scanning the plugin DLLs the various classes are registered like so:
var assemblyTypes = assembly.GetTypes();
var settingsTypes = FindType(assemblyTypes, typeof(ISettings));
var settingsType = settingsTypes.First();
builder.RegisterType(settingsType).Named<ISettings>(settingsName);
Similar registering is done for IService and IRepository and here's how a client is fired up:
var client = scope.ResolveNamed<IClient>(clientName,
new NamedParameter("settings", settings),
new NamedParameter("repository", repository));
This gives me an IClient based on the clientName which in this case is specified on the command line (as is the appropriate settings file path to use for this client).
Getting to really like the features of Autofac!