Using Autofac to find plugins - c#

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!

Related

Adding Service from dynamically loaded Assembly to ServiceCollection?

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

MAUI dependency injection service resolve in Program.cs

MAUI has dependency injection setup similar to what ASP.NET Core has in the Startup.cs class. This one is set in MauiProgram.cs file by default.
My question is: How can I get a service instance in this file after services registration? I guess, one solution will be the following, but then I must edit this code also if the constrctors of these services change during time:
var keyValueStore = new PreferencesKeyValueStore();
var accountService = new AccountService(keyValueStore);
var profileService = new ProfileService(keyValueStore);
builder.Services.AddSingleton<IKeyValueStore>(keyValueStore);
builder.Services.AddSingleton<IAccountService>(accountService);
builder.Services.AddSingleton<IProfileService>(profileService);
//Here now I can use accountService and profileService to do something
I can not find more elegant solution that will return the service instance for me from the DI container. Something like:
builder.Services.AddSingleton<IKeyValueStore, PreferencesKeyValueStore>();
builder.Services.AddSingleton<IAccountService, AccountService>;
builder.Services.AddSingleton<IProfileService, ProfileService>();
//Now I can't perform something like: var accountService = diContainer.GetInstance<IAccountService>(); or similar.
I don't know how to reach di container and ask it to provide me registered instance.
Actually, the documentation provided a simple way to do so.
Check it here
They recommended to use the Handler property of any object of type Element, there you can write the code :
// Considering you want to resolve a service from your custom interface IMyService
var service = this.Handler.MauiContext.Services.GetService<IMyService>();
// Then you can use the resolved service..
But there are some issues, personally it never worked for me, the Handler property may be null because of the lifecycle of the Element you are calling it on.
To avoid this issue, use a full line like:
var service = Application.Current.MainPage
.Handler
.MauiContext
.Services
.GetService<IMyService>();
// Then you can use the resolved service..
This works fine for me
Hope it helps you ..

How to instantiate outside of a constructor?

How to replicate this code with Autofac syntax?
public static class MenuConfig
{
public static void Initialize()
{
var _menuService = DependecyFactory.GetInstance<IMenuService>();
Parameters.Menu = _menuService.Menu();
}
}
Before calling this a "duplicate question" please note that I'm looking for an Autofac command. I CANNOT inject the interface anywhere and then call "Resolve". What I need to is perform an "InstancePerRequest" inline and uninjected so I don't have to do this:
var _service = new Service(new Dependency(new context()));
LightInject has a method that allows instantiation from an interface OUTSIDE of a constructor like this:
var _service = DependecyFactory.GetInstance<IService>();
What is the equivalent method for Autofac?
When calling containerBuilder.Build() you get back a container which implements IContainer and ILifetimeScope, whenever you get hold of one of these interfaces, you can resolve types from it:
container.Resolve<IService>();
If you want this container to be static, you could add the container as a static property to the Program or Startup class (depending if you're creating a Console or ASP.NET application).
Remember that the root container will be around for the entire duration of your application, so this can result in unwanted memory leaks when used incorrectly. Also see the warning in the documentation.
Still, it's perfectly possible to do the memory management yourself by resolving an Owned<> version from your interface:
using (var service = Program.Container.Resolve<Owned<IService>>())
{
service.Value.UseService();
}
Anyway, since you mention a static class in the comments, the best solution is to change that into a non-static class and register it as a singleton with Autofac. Then you can inject a Func<Owned<IService>> serviceFactory into that singleton and create/dispose an instance of the service wherever you need it.
using (var service = serviceFactory())
{
service.Value.UseService();
}
This is simply not possible with Autofac. All other solutions involving Autofac will require code refactoring which may potentially break software functionality. So unfortunately, the most elegant and least disruptive solution is this:
var _service = new Service(new Dependency(new context()));
Since this is an edge case addressing only one part of the software, this compromise is acceptable. It would be nice, however, if Autofac implemented this functionality in some future release.

ASP.NET Core access service in Startup.cs ConfigureServices method

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...

How to invoke WCF service with own constructor using my InstanceProvider

I am kind of new in implementing and using WCF services and extremely new (and apparently clueless) in DI.
I have WCF Services which are having constructors. The parameters of the constructors could only come in runtime from the Client application (Web server).
Something like this:
In Application server:
public class MyService : IMyService {
private IUserContext userContext;
public MyService(IUserContext uContext) {
this.userContext = uContext;
}
public DoWork() {
... // uses uContext
}
}
In Web server can only see IMyService and not the implementation of the MyService. The code would be something like this (oversimplified console app):
class Program {
static void Main(string[] args) {
var factory = new ChannelFactory<IMyService>("MyServiceEndpoint"); // MyServiceEndpoint correctly defined in config file
var client = factory.CreateChannel();
client.DoWork();
((IClientChannel)client).Close();
factory.Close();
}
}
First WCF "forced" me to use parameter-less constructor in the implementation of MyService in order to test it I added that by initializing the UserContext object. Of course I don't have the necessary info to create the object in compile time so this won't help me.
I proceeded with using this solution creating my own ServiceHostFactory, ServiceHost and IInstanceProvider where IDependency is an interface IUserContext which is implemeted by my UserContext class.
This works as expected, I registered in my svc file the custom factory, I don't need parameter-less constructor anymore. However since I don't know how to pass my UserContext to the InstanceProvider I only get a default UserContext object.
Now my noviceness comes in. I don't know how to invoke MyService by passing in the UserContext which lives in the web server. Do I also need own ChannelFactory?
Can someone direct me in the right way by updating the web server dummy code?
Thanks!
Remark: I don't want UserContext to be a parameter of the DoWork() method, because that would mean changing the parameter list of all my services and all calls...
The notion of constructors does not exist on the wire (no matter what transport you are using). For that reason you will never be able to make the client invoke a particular constructor. This is simply not part of the design of WCF (also not part of SOAP).
Don't use constructor parameters that are provided by the client. Or, make the service class have a parameterless ctor and make all service methods accepts the former constructor parameters as normal parameters.
You can also transmit common parameters as SOAP headers. That saves you changing the signature of all service methods.

Categories