How to setup composite class with microsoft dependency injection? - c#

this is dotnet.core question
let's say i have class NotifierResolver:
public class NotifierResolver : INotifierResolver
{
private readonly List<INotifier> _notifiers;
public NotifierResolver(List<INotifier> notifiers)
{
_notifiers = notifiers;
}
public INotifier Resolve(INotification notification)
{
var notifier = _notifiers.Single(h => h.CanNotify(notification));
if (notifier == null)
{
throw new Exception($"could not resolve notification: {notification.GetType()}");
}
return notifier;
}
}
How do i setup it's dependencies under service collection ( Microsoft.Extensions.DependencyInjection ) ?
var serviceProvider = new ServiceCollection()
.????
.AddSingleton<INotifierResolver, NotifierResolver>()
.BuildServiceProvider();

You may need to build up the composite at the composition root like below.
var serviceProvider = new ServiceCollection()
.AddTransient<INotifier, SomeNotifier>()
.AddTransient<INotifier, SomeOtherNotifier>()
.AddTransient<INotifier, YetAnotherNotifier>()
.AddSingleton<INotifierResolver, NotifierResolver>(_
=> new NotifierResolver(_.GetServices<INotifier>().ToList())//<-- Note GetServices
)
.BuildServiceProvider();
So assuming you have multiple INotifier registered with the service collection, when resolving the INotifierResolver, the provider with resolve all the dependencies via IServiceProvider.GetServices extension method and inject them into the dependent class.

Related

Autofac can't resolve dependency

I can't resolve registered type IQMService.
Error:No accessible constructors were found for the type '__ASP.FastObjectFactory_app_web_gmbeg5il'.
Below is the code invoke Resolve:
public IQMService qMService;
public QMModule QMModule;
public PageBase() {
qMService = AutofacDependency.Resolve<IQMService>();
QMModule = new QMModule(qMService);
}
Definition of model:
public interface IQMService{
somefunction()
}
public class QMService : IQMService{
public QMService()
{
someprocess();
}
}
Previously working code of AutofacDependency() defined:
static AutofacDependency()
{
Assembly[] assemblys = AppDomain.CurrentDomain.GetAssemblies ();
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes (assemblys);
builder.RegisterModule (new ConfigurationSettingsReader ("autofac"));
container = builder.Build();
}
After I upgrade .net from 4.0 to 4.8, and update autofac from 3.5.2 to 6.4.0, ConfigurationSettingsReader no longer exist, so I have to update the config file and change the code to below, not work,
static AutofacDependency()
{
Assembly[] assemblys = AppDomain.CurrentDomain.GetAssemblies ();
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes (assemblys);
//builder.RegisterModule (new ConfigurationSettingsReader ("autofac"));
ConfigurationBuilder cfgbuilder = new ConfigurationBuilder();
cfgbuilder.AddXmlFile("autofac.config");
var module = new ConfigurationModule(cfgbuilder.Build());
builder.RegisterModule(module);
container = builder.Build();
}
I also test this way
static AutofacDependency()
{
Assembly[] assemblys = AppDomain.CurrentDomain.GetAssemblies ();
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes (assemblys);
builder.RegisterType<Strix.QM.Service.QMService>().As<Strix.QM.Service.IQMService>().InstancePerLifetimeScope();
container = builder.Build();
}
But I always get the error No accessible constructors were found even though I can it's defined. Thank you if any help, and if any necessary information missing please comment I will append it.
The short version here is that this...
Assembly[] assemblys = AppDomain.CurrentDomain.GetAssemblies ();
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes (assemblys);
...is really not something you should ever do. This literally registers every type in the entire running system, including everything in every .NET base library, into dependency injection. Be way, way more selective than that when registering. I'd bet if you remove that and only register the stuff you need, then DI won't try to resolve the odd .NET stuff that doesn't have accessible constructors.

Registering more AmazonS3Client with configurations on Autofac

I added AWSSDK.S3 to my project because I want to use S3FileInfo to access files on S3 in a clean way, and I registered an instance of the AmazonS3Client on Autofac to get it in my services.
Something like this for the registration on Autofac:
builder.Register(context => {
var credentials = new BasicAWSCredentials("accessKeyId", "SecretAccessKey");
var config = new AmazonS3Config {
RegionEndpoint = RegionEndpoint.GetBySystemName("regionEndpoint")
};
return new AmazonS3Client(credentials, config);
}).As<IAmazonS3>().SingleInstance();
The point is that if I want to add more configurations to access different buckets on different accounts like this I cannot.
What's the cleanest way to register on Autofac more instances of IAmazonS3 with different configurations?
There is many way to do what you want. It depends on where you get your credentials.
If you have know the credentials when autofac is building you can use named instance
builder.Register(context => {
var credentials = new BasicAWSCredentials("accessKeyId1", "SecretAccessKey1");
var config = new AmazonS3Config {
RegionEndpoint = RegionEndpoint.GetBySystemName("regionEndpoint")
};
return new AmazonS3Client(credentials, config);
}).Named<IAmazonS3>("client1").SingleInstance();
builder.Register(context => {
var credentials = new BasicAWSCredentials("accessKeyId2", "SecretAccessKey2");
var config = new AmazonS3Config {
RegionEndpoint = RegionEndpoint.GetBySystemName("regionEndpoint")
};
return new AmazonS3Client(credentials, config);
}).Named<IAmazonS3>("client2").SingleInstance();
To resolve them you can use IIndex<String, IAmazonS3> or use the WithParameter method at registration or an autofac module.
More information on Named instances are available on the documentation : Named and Keyed services
If you have the credentials at runtime you can resolve a factory. let's say ServiceX needs a IAmazonS3Client instance you can have a dependency on Func<BasicAwsCredentials, AmazonS3Config, IAmazonS3Client> and Autofac will do the magic for you.
public class ServiceX
{
public ServiceX(Func<BasicAwsCredentials, AmazonS3Config, IAmazonS3Client> factory)
{
this._amazonS3Factory = factory;
}
private readonly Func<BasicAwsCredentials, AmazonS3Config, IAmazonS3Client> _amazonS3Factory;
public void Do()
{
IAmazonS3Client client = this._amazonS3FActory(credentials, config);
// do something with client
}
}
Not finding an answer that fits, after thinking a bit about it I decided to follow this approach, probably not very clean but it works:
public interface IAmazonS3FirstConfig : IAmazonS3
{
}
public class AmazonS3ClientFirstConfig : AmazonS3Client, IAmazonS3FirstConfig
{
public AmazonS3ClientFirstConfig(BasicAWSCredentials credentials, AmazonS3Config config)
: base(credentials, config)
{
}
}
Using the ad-hoc class and interface to register this configuration, and another couple for the second configuration.

Using Autofac in integration tests with web api 2

I have been manually instantiating my services in my integration tests, but when I got to a serve that had Lazy dependencies, I did some research and found that you can actually use Autofac to resolve your services when doing your tests.
So, I wrote this class:
public class Container<TModule> where TModule: IModule, new()
{
private readonly IContainer _container;
protected Container()
{
var builder = new ContainerBuilder();
builder.RegisterModule(new TModule());
_container = builder.Build();
}
protected TEntity Resolve<TEntity>() => _container.Resolve<TEntity>();
protected void Dispose() => _container.Dispose();
}
And then in my context, I changed to this:
public class ProductContext : Container<AutofacModule>
{
public IProductProvider ProductProvider { get; }
public static ProductContext GiventServices() => new ProductContext();
protected ProductContext()
{
ProductProvider = Resolve<IProductProvider>();
}
public List<JObject> WhenListProducts(int categoryId) => ProductProvider.List(categoryId);
}
I have another context that seems to work (the tests pass) and that is using a MatchProvider. If I compare both in my Autofac module, they look like this:
builder.RegisterType<ProductProvider>().As<IProductProvider>().InstancePerRequest();
and
builder.RegisterType<MatchProvider>().As<IMatchProvider>().SingleInstance();
Because the MatchProvider is a singelton, it seems to have no issues being resolved, but the ProductProvider is an instance per request, this is where the issue seems to lie.
I get this error when running any tests that require that service:
No scope with a tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested.
I figured it was because I didn't have the right nuget packages installed. So I installed:
Autofac
Autofac.Integration.Owin
Autofac.Integration.WebApi
Autofac.Integration.WebApi.Owin
These are the same references that are used where my module is defined, but this did not help.
Does anyone know what I need to do to get this to work?
I couldn't find a suitable (easy) solution to this. I saw some people creating lifetime scopes themselves, which to me seemed like overkill and it wasn't "nice" code.
So, taking one of Autofac's principles: Any service that is registered multiple times; the last instance is the instance that is resolved.
So in my Container class, I just re-registered my InstancePerRequest services as InstancePerDependency instead. This solved my issue.
Here is my full code:
public class ContainerContext<TModule> where TModule: IModule, new()
{
private IContainer _container;
protected ContainerContext()
{
var builder = new ContainerBuilder();
builder.RegisterModule(new TModule());
// Because Autofac will resolve services using the last registration, we can change all our web api
// services to by InstancePerDependency instead of InstancePerRequest which is obviously better
// when testing.
builder.RegisterType<AnswerProvider>().As<IAnswerProvider>().InstancePerDependency();
builder.RegisterType<AttributeProvider>().As<IAttributeProvider>().InstancePerDependency();
builder.RegisterType<CategoryProvider>().As<ICategoryProvider>().InstancePerDependency();
builder.RegisterType<ClaimProvider>().As<IClaimProvider>().InstancePerDependency();
builder.RegisterType<ClientProvider>().As<IClientProvider>().InstancePerDependency();
builder.RegisterType<CriteriaProvider>().As<ICriteriaProvider>().InstancePerDependency();
builder.RegisterType<FeedProvider>().As<IFeedProvider>().InstancePerDependency();
builder.RegisterType<FormulaProvider>().As<IFormulaProvider>().InstancePerDependency();
builder.RegisterType<ImageProvider>().As<IImageProvider>().InstancePerDependency();
builder.RegisterType<GroupProvider>().As<IGroupProvider>().InstancePerDependency();
builder.RegisterType<QuestionProvider>().As<IQuestionProvider>().InstancePerDependency();
builder.RegisterType<StripeProvider>().As<IStripeProvider>().InstancePerDependency();
builder.RegisterType<ApiAiProvider>().As<IApiAiProvider>().InstancePerDependency();
builder.RegisterType<PiiikProvider>().As<IPiiikProvider>().InstancePerDependency();
builder.RegisterType<ProductProvider>().As<IProductProvider>().InstancePerDependency();
builder.RegisterType<SettingProvider>().As<ISettingProvider>().InstancePerDependency();
builder.RegisterType<TrackingProvider>().As<ITrackingProvider>().InstancePerDependency();
_container = builder.Build();
}
protected TEntity Resolve<TEntity>() => _container.Resolve<TEntity>();
protected void Dispose() => _container.Dispose();
}
And then, any context I use inherits this class:
public class ProductContext : ContainerContext<AutofacModule>
{
public IProductProvider ProductProvider { get; }
public static ProductContext GiventServices() => new ProductContext();
protected ProductContext()
{
ProductProvider = Resolve<IProductProvider>();
}
public List<JObject> WhenListProducts(int categoryId) => ProductProvider.List(categoryId);
}
Which means, when testing, I can just do this:
[TestFixture]
public class ProductProviderTests
{
[Test]
public void ShouldHaveProducts()
{
var services = ProductContext.GiventServices();
var products = services.WhenListProducts(1);
products.Count.Should().NotBe(0);
}
[Test]
public void ShouldHaveDuplicateVariants()
{
var services = ProductContext.GiventServices();
var products = services.WhenListProducts(1);
var anyDuplicate = products.GroupBy(x => x.SelectToken("variant").ToString()).Any(g => g.Count() > 1);
anyDuplicate.Should().Be(true);
}
[Test]
public void ShouldNotHaveDuplicateGtins()
{
var services = ProductContext.GiventServices();
var products = services.WhenListProducts(1);
var anyDuplicate = products.GroupBy(x => x.SelectToken("gtin").ToString()).Any(g => g.Count() > 1);
anyDuplicate.Should().Be(false);
}
}
This should help anyone else having the same issue.

How can I implement Named Service with parameters in Autofac?

I've a concrete Service and it's behaviour differs by it's parameters, I could not achieve to Register and Resolve it by Autofac. As you can see it is so easy to implement it by custom Container. How can I use Autofac for this requirement?
public class Container
{
Dictionary<string, MyService> _components = new Dictionary<string, MyService>();
void Register(string key,string param, string param2)
{
_components.Add(key, new MyService(param, param2, ResolveRepository()));
}
MyService ResolveMyService(string key)
{
return _components[key];
}
IRepository ResolveRepository()
{
throw new NotImplementedException();
}
}
public class MyService
{
public MyService(string param,string param2,IRepository rep ) { }
}
public interface IRepository { }
EDIT: I'm trying the solve registration in Autofac, but we have no Container during Registration process.
builder.RegisterType<MyService>()
.Named<MyService>("Service1")
.OnActivating(e =>
{
e.ReplaceInstance(new MyService("Service1", "param1-23", Container.Resolve<IRepository>()));
});
You can access the container during the Activating pseudo event by using e.Context
builder.RegisterType<MyService>()
.Named<MyService>("Service1")
.OnActivating(e =>
{
MyService s = new MyService("Service1",
"param1-23",
e.Context.Resolve<IRepository>())
e.ReplaceInstance();
});
but another option would be to use the WithParameter method.
builder.RegisterType<MyService>()
.Named<MyService>("Service1")
.WithParameter("param1", "Service1")
.WithParameter("param2", "param1-23");

Inject into asp.net web api model with autofac

I want to use IValidatableObject.Validate() to check some aspects of my model against a repository before processing requests. However, with the config below _dalForValidation is never set in on Models.App, in other words the default empty constructor is always being called.
private static void ConfigureAutofac()
{
var builder = new ContainerBuilder();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterType<DataAccessFacade>().As<IDataAccess>().InstancePerApiRequest();
builder.RegisterType<Models.App>();
var container = builder.Build();
var resolver = new AutofacWebApiDependencyResolver(container);
GlobalConfiguration.Configuration.DependencyResolver = resolver;
}
App has 2 constructors:
public App(IDataAccess dalForValidation)
{
_dalForValidation = dalForValidation;
}
public App() {}
for completeness this is where I try to access it, getting a null reference exception:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var existingApps = _dalForValidation.FindApps().Convert<DB.App,App>();
if (!ValidateProxyMappings(existingApps))
yield return new ValidationResult("Invalid proxy mapping");
}
Perhaps the dependency resolver is not used for the model, or is there something else I'm missing here?

Categories