Autofac: resolving different params to object using DependencyResolver - autofac

I am building an MVC application and have the need to send a different cachecontext (wraps info on which cache to use) into my repositories using the DependencyResolver. There are 10 different types of cachecontexts that can be passed in. At the moment, I am using 10 different registrations for 10 different types of interfaces (ICacheContexts). This works but does not seem like the correct way of handling this circumstance. Is there a way to create a factory that knows the object type it is being passed to so I can just determine the correct CacheContext to return and thus only have one registration to maintain?
Current code:
builder.Register(c => new CacheContext(AppConfig.AppBucketName, AppConfig.AppBucketpassword))
.As<ICacheContextForApps>();
builder.Register(c => new CacheContext(AppConfig.AcctBucketName, AppConfig.AcctBucketpassword))
.As<ICacheContextForAccounts>();
etc..

You need to do this from the repository side - for each repository, configure its parameters to resolve the right cache context. E.g. (paraphrasing)
builder.Register(c => new CacheContext(...for appls...))
.Named<ICacheContext>("apps");
builder.RegisterType<AppsRepository>()
.WithParameter((pi, c) => pi.ParameterType == typeof(ICacheContext),
(pi, c) => c.ResolveNamed<ICacheContext>("apps"));
Hope this gets you on the right track.

Related

Choose dependency for injection using Kiwi in Flutter

I have the following code:
KiwiContainer()
..registerFactory((c) => GetOrderListOfItemsUseCase(
repository: c<ShopRepository>()))
The problem here is that the GetOrderListOfItemsUseCase requires an instance of IProfileRepository which the ShopProfileRepository extends from.
NOTE: IProfleRepository is extended by IUserProfileRepository and IShopProfileRepository.
UPDATE: I have the following factories:
..registerFactory<IShopRepository>((c) => c<ShopRepository>())
..registerFactory<IUserProfileRepository>((c) => c<UserProfileRepository>())
The thing is that I want to dynamically inject the proper dependency for GetUserLocationsUseCase.
..registerFactory(
(c) => GetUserLocationsUseCase(repository: c<IProfileRepository>()))
how can I inject in the code the proper subtype of IProfileRepository whether it IShopRepository or IUserProfileRepository?
If you want to dinamically inject either UserProfileRepository or ShopProfileRepository you should register the one you are going to use via its supertype.
KiwiContainer()
..registerFactory<IProfleRepository>((c) => ShopProfileRepository())
..registerFactory((c) => GetOrderListOfItemsUseCase(
repository: c<IProfleRepository>()))
And obtain it via its supertype too, like in the snippet.

Autofac Multi Tenant override and IEnumerable<T> inject/resolve

Please forgive my non-native English:
In short, What is the best way for a tenant to override default IEnumerable<T> registration?
TL;DR So I have a service ServiceToBeResove(IEnumerable<IShitty> svcs) need an IEnumerable<IShitty> dependency, but we found not all our tenants have services registered as IShitty, so in our application container we create an not implemented NoImplementShitty and register it as a TypeService of IShitty to server as a default one to make resolve process happy, we do get tenant-specific if tenant have registration and this default non-implemented if tenant forgot to register. But we soon find the ServiceToBeResove will have both tenants implemented registered IShitty and the default NoImplementShitty for its dependence of IEnumerable. What I really want for the IEnumerable<IShitty> dependency is just used tenant registered (registered 1 or more), if tenant not registered, just use the default NoImplementShitty as the IEnumerable<IShitty>. I have played with .OnlyIf(), OnlyIfRegistered(), .PreventDefault() on the app container and it really not helps since autofac will build default first and then tenant. I can certainly use the NoImplementShitty for all the tenant that missing registration of IShitty but it doesn't seem to take the advantage of multiple tenant's override-default features.
To be more specific, In our base AgreementModule, we have
builder.RegisterType<NoOpAgreementHandler>() //NoOpAgreementHandler is the IShitty
.As<IAgreementHandler>()
.InstancePerLifetimeScope();
In our tenantA, we have
public class TenantAContainerBuilder : ITenantContainerBuilder
{
public virtual object TenantId => "1";
public virtual void Build(ContainerBuilder builder)
{
builder.RegisterType<TenantAAgreementHandler>()
.As<IAgreementHandler>()
.InstancePerLifetimeScope();
}
}
We build container as below:
var appContainer = builder.Build();
var tenantIdentifier = new ManualTenantIdentificationStrategy(); //We have our own strategy here I just use the ManualTenantIdentificationStrategy for example
var multiTenantContainer = new MultitenantContainer(tenantIdentifier, appContainer);
//GetTenantContainerBuilders will basically give you all TenantBuilder like TenantAContainerBuilder above
foreach (IGrouping<object, ITenantContainerBuilder> source in GetTenantContainerBuilders().GroupBy(x => x.TenantId))
{
var configurationActionBuilder = new ConfigurationActionBuilder();
configurationActionBuilder.AddRange(source.Select(x => new Action<ContainerBuilder>(x.Build)));
multiTenantContainer.ConfigureTenant(source.Key, configurationActionBuilder.Build());
}
When try to resolving the service, if we do:
public DisbursementAgreementManager(IEnumerable<IAgreementHandler> agreementHandlers)
{
_agreementHandlers = agreementHandlers;
}
The agreementHandlers will be an IEnumerable of NoOpAgreementHandler and TenantAAgreementHandler, seems wierd to have NoOpAgreementHandler and I thought we will only get TenantAAgreementHandler. But if we change the DisbursementAgreementManager to
public DisbursementAgreementManager(IAgreementHandler agreementHandler)
{
_agreementHandler = agreementHandler;
}
We will get only the TenantAAgreementHandler which is expected.
The default behavior of Autofac is there for a reason. Asking it to do it differently would be adding application logic at the dependency-injection level, which violates the separation of concerns (DI should only inject dependencies) and leads directly to surprising behavior ("Why did DI not inject every available component?") and undercuts the maintainability of the system.
This may be a non-issue.
The logic is self-contained inside each IAgreementHandler.
If so, at the point where they are invoked by DisbursementAgreementManager, they are all called and then perform their own logic (which may include a decision whether to do anything all). E.g.:
foreach (var ah in _agreementHandlers) ah.Agree(disbursementInfo);
or maybe something like
foreach (var ah in _agreementHandlers.Where(a => a.ShouldRun(data) || overridingCondition))
{
var agreement = ah.Agree(info);
this.Process(agreement);
}
or whatever. The point is that if NoOpAgreementHandler is doing what it is supposed to (that is, nothing) then it should have no effect when it is called. No problem.
If the situation is other than described, then NoOpAgreementHandler and possibly IAgreementHandler need to be refactored.
There is another point of concern:
The reason we add the no-op is we have unit tests for registration/resolve in order to make sure all registration is properly configured.
Your testing requirements are bleeding into your primary logic. These DI configuration tests should be independent of the production DI configuration. NoOpAgreementHandler shouldn't even be in your primary project, just a member of the unit test project.

What is InstancePerLifetimeScope in Autofac?

Can someone please explain in plain English what the lines of code where I put the question marks do? Or maybe point me to an article that puts light on this. This code is for registering dependencies in an autofac container
var builder = new Autofac.ContainerBuilder();
builder.Register<NHibernateInstance>(c =>
new NHibernateInstance(ConnString, false))
.InstancePerDependency();//?????
builder.Register(c => c.Resolve<NHibernateInstance>()
.GetFactory().OpenSession())
.As<ISession>()
.InstancePerLifetimeScope(); //-----?????
This is a dependency injection container. The Autofac.ContainerBuilder gets a new container, or registrar you might say.
The builder.Register<NHibernateInstance> is stating that when constructing an NHibernateInstance during the recovery phase (i.e. getting an instance out of the container) this is how it should be built.
The last line is indicating that when resolving an NHibernateInstance the OpenSession method should be called once per the lifetime of the object.

Make use of AllTypes.FromAssemblyContaining() / UsingFactoryMethod()

I am registering many repositories which are located in the same assembly by using the following code:
IoCContainer.Register(AllTypes.FromAssemblyContaining<RepositoryOne>).BasedOn(typeof(IRepository<>)).WithService.AllInterfaces().Configure(c => c.LifeStyle.Singleton));
Because I want to have influence on the creation of these repositories, I am trying to use UsingFactoryMethod() with it. Using this method is simple when I register every repository separately like
IoCContainer.Register(Component.For<IRepositoryOne>().ImplementedBy<RepositoryOne>().LifeStyle.Singleton.UsingFactoryMethod(() => Factory.New<RepositoryOne>()));
...
IoCContainer.Register(Component.For<IRepositoryN>().ImplementedBy<RepositoryN>().LifeStyle.Singleton.UsingFactoryMethod(() => Factory.New<RepositoryN>()));
But how can I use UsingFactoryMethod() together with the code from the first example?
TIA
You have to use a bit of reflection to use it, since you don't have the exact type of the object you'd be resolving.
var factoryMethod = typeof(Factory).GetMethod("New", BindingFlags.Static|BindingFlags.Public);
container.Register(
AllTypes.FromAssemblyContaining<RepositoryOne>)
.BasedOn(typeof(IRepository<>))
.WithService.AllInterfaces()
.Configure(x => x.UsingFactoryMethod((k, c) => factoryMethod.MakeGenericMethod(c.RequestedType).Invoke(null, null)));

Correct routing for a Rest API with Zend

I'm trying to implement a REST API to my website.
My problem is that the default Zend routing gets in the way. I've first tried using Zend_Rest_Route but I haven't been able to understand how I was supposed to use it correctly for "deep" routes, aka website/api/resource1/filter/resource2/id.
Using the default Zend routing, I'd need to create a gigantic Resource1Controller to take care of all the possible actions, and I don't think it's the "good" way to do this.
I've tried using Resauce ( http://github.com/mikekelly/Resauce/), creating an api module and adding routes, but I'm not able to get it working correctly :
The patterns I added were :
$this->addResauceRoutes(array(
'api/resource' => 'resource',
'api/resource/:id' => 'custom',
'api/resource/filter' => 'resource-filter',
'api/resource/filter/:id' => 'custom',
));
Which then leads to this :
public function addResauceRoutes($routes) {
$router = Zend_Controller_Front::getInstance()->getRouter();
foreach ($routes as $pattern => $controller) {
$router->addRoute($controller,
new Zend_Controller_Router_Route($pattern, array(
'module' => 'api',
'controller' => $controller
)
)
);
}
Zend_Controller_Front::getInstance()->setRouter($router);
website/api/resource gets me the
Resource1Controller, ok
website/api/resource/filter gets me to the
resource1filterController, ok
website/api/resource/filter/:id gets me to
a custom controller, ok
I'd like for website/api/resource/:id to get me to the same custom controller... But it redirects me to the Resource1Controller.
What solution is there for me to correctly create my API ? Is there a good way to do this with Zend_Rest_Route ?
Edit : Mike,
I felt that it was not appropriate for me to use different controllers since I need the pathes "website/api/resource/:id" and "website/api/resource/filter/:id" to give me almost the exact same result (the only difference is that because the filter is there, I may get a message telling "content filtered" here).
I thought it was a waste creating another almost identical controller when I could've used the same controller and just checked if a parameter "filter" was present.
However, I don't want to use the basic Zend routing since for the path "website/api/resource/filter/resource2" I'd like to have a totally different comportment, so I'd like to use another controller, especially since I'm trying to use Zend_Rest_Action and need my controllers to use the basic actions getAction(), putAction(), postAction() and deleteAction().
Please could you explain why it is you need two URI patterns pointing to the same controller. A better solution might be to use a separate controller for each of the two patterns and move any shared logic into your model.
Forcing a unique controller for each routing pattern was an intentional design decision, so I'd be interested to hear more detail about your use case where you feel this isn't appropriate.
I thought it was a waste creating
another almost identical controller
when I could've used the same
controller and just checked if a
parameter "filter" was present.
Personally, I think it is cleaner to move the shared logic into the model and to keep your controllers skinny. To me it's not wasteful, it's just more organised - it will make your code easier to manage over time.
If you really need to use the same controller you could always use a query parameter instead, that would work fine:
api/resource/foo?filter=true
That URI would be taken care of by the first route ('api/resource/:id' => 'custom') for free.
But please consider using two controllers, I think that is a better approach.
Okay, the reason I didn't get the good controllers was because Resauce uses the controller name as the name of the route, which has to be unique - so second url pointing to "custom" controller couldn't work. Now I'm able to get the files I want :)
So instead of what was previously noted, I use directly the $router->addRoute(); and define new names each times, even if pointing to the same controller.
Example :
$router->addRoute('resource', new Zend_Controller_Router_Route('/api/resources/:id', array('module' => 'api', 'controller' => 'resource')));
$router->addRoute('resourceFiltered', new Zend_Controller_Router_Route('/api/resources/filter1/:id', array('module' => 'api', 'controller' => 'resource', 'filter' => 'filter1'));