1

As detailed in InstancePerApiControllerType not working, I am unable to use the InstancePerApiControllerType to configure my solution. The answer provided there works so long as I am directly injecting a ConnectionContext into the controller, or otherwise know that a class is only used by a specific controller. Unfortunately that is not the case in my situation:

ControllerA -> EngineA -> RepositoryA -> GenericEntityAccessor

ControllerB -> EngineB -> RepositoryB -> GenericEntityAccessor

The issue is when we come in through ControllerA, GenericEntityAccessor needs "string A" and from ControllerB it needs "string B".

Of course, the real situation is a little more complicated and there are some bad practices such as code that directly "news"-up a ConnectionContext (it's legacy code). I'm currently exploring providing another component that provides the connection string that is injected via Autofac and configured in the controller using Lazy, but the bad practices are causing problems there also (i.e. once I start to change things in the interface, all the dominoes start to fall over and I end up 15 classes later wondering how I got there).

Are there any patterns, techniques, etc. that address this type of thing? I can't imagine it's all that uncommon.

UPDATE:

To provide a few more specifics, since I'm having some trouble getting this to work, in general we have the following hierarchy, showing which scopes I've applied

Controller -> InstancePerApiRequest()
I*Repository -> ?
I*Manager -> ?
I*Builder -> ?
I*Adapter -> ?
ISqlServerConnectionContext -> ?
IConnectionContextCache -> InstancePerApiRequest()

I've got a number of components that directly take ISqlServerConntectionContext and I'm trying to provide it like so:

container.Register(c =>
{
    var connectionContextCache = c.Resolve<IConnectionContextCache>();
    var connection = (ISqlServerConnectionContext)connectionContextCache.CurrentConnectionContext;

    return connection;
}).As<ISqlServerConnectionContext>().InstancePerDependency();

Unfortunately at that point I'm getting a null for CurrectConnectionContext. My guess at this point is I've got some component that isn't rooted from the controller and I'm currently going through the dependencies manually attempting to find it (AFAIK the isn't a way for my to find out which object triggered Autofac to attempt to provide the ISqlServerConnectionContext when I'm debugging).

UPDATE 2:

It turns out I did have some issues where I was registering things improperly, and creating a dependency on ISqlServerConnectionContext for DocumentController, even though it did not have one (this was created through the delegate for something it did depend on).

Now I've got a circular reference that I'm pretty sure I've created myself in the registrations:

container.Register(x =>
{
    if (x.IsRegistered<HttpRequestMessage>())
    {
        var httpRequestMethod = x.Resolve<HttpRequestMessage>();

        var tokenHelper = x.Resolve<ITokenHelper>();
        var token = tokenHelper.GetToken(httpRequestMethod);

        return token ?? new NullMinimalSecurityToken();
    }

    return new NullMinimalSecurityToken();
}).As<IMinimalSecurityToken>().InstancePerApiRequest();

container.Register(c =>
{
    var connectionContextCache = c.Resolve<IConnectionContextCache>();
    var token = c.Resolve<IMinimalSecurityToken>();
    var connection = (ISqlServerConnectionContext)connectionContextCache.CurrentConnectionContext;

    connection.Token = token;

    return connection;
}).As<ISqlServerConnectionContext>().InstancePerApiRequest();

The problem is ISqlServerConnectionContext has a property of type IMinimalSecurityToken which is optional, and definitely not used when the ISqlServerConnectionContext is being used to look up IMinimalSecurityToken, which depends on ISqlServerConnectionContext through ITokenHelper.

UPDATE 3:

For completeness, in order to solve my circular reference problem I needed to use named services, and use a SqlServerConnectionContext that did not have the IMinimalSecurityToken property set for the IOAuthTokenManager registration. Now I'm getting the dreaded

No scope with a Tag matching 'AutofacWebRequest' is visible

error, but I think that warrants a new question if I'm not able to solve it.

container.Register(c =>
{
    var productId = WellKnownIdentifierFactory.Instance.GetWellKnownProductIdentifier(WellKnownProductIdentifiers.RESTSearchService);
    var connectionString = ConfigurationManager.AppSettings[AppSettingsNames.DatabaseConnection];

    var newConnectionContext = new SqlServerConnectionContext(connectionString) { ProductID = productId };
    newConnectionContext.Open();

    return newConnectionContext;
}).Named<ISqlServerConnectionContext>("OAuthTokenConnectionContext").InstancePerApiRequest();
container.Register(c => new SqlServerBuilderFactory(c.ResolveNamed<ISqlServerConnectionContext>("OAuthTokenConnectionContext"))).Named<IBuilderFactory>("OAuthTokenBuilderFactory").InstancePerApiRequest();
container.Register(c =>new OAuthTokenManager(c.ResolveNamed<IBuilderFactory>("OAuthTokenBuilderFactory"))).As<IOAuthTokenManager>().InstancePerApiRequest();
Community
  • 1
  • 1
Colin Young
  • 3,018
  • 1
  • 22
  • 46
  • Is it possible to put null referencing checks in the CurrentConnectionContext property and include the current StackFrame method name in a thrown InvalidOperationException ? – Matt Caton Jul 21 '14 at 18:45
  • Could be that you have registered the cache as perlifetimescope. Remember that perlifetimescope in a web context is actually the root scope and not scoped to the request. Try updating to perapirequest as per your controllers. Just seen my answer made this mistake! Apologies that was an auto complete bug and I'll update ASAP. – Matt Caton Jul 21 '14 at 19:34
  • See this question regarding debugging AutoFac resolution: http://stackoverflow.com/questions/18578942/how-can-i-log-all-resolve-requests-to-autofac-container – Matt Caton Jul 21 '14 at 19:37
  • I was wondering about the PerLifetimeScope on the cache. Switched that and still getting the same issue. Adding the current StackFrame method in the exception is just giving me something along the lines of b__f. Basically I can see the container is having a problem, but not which bit of the configuration is causing it. I did new up a connection in that method with a bad connection string in hopes of figuring out which component gets it. That's where I'm at now, trying to figure out which one it is. – Colin Young Jul 21 '14 at 20:25
  • Thanks for the updates - very puzzling. The fact that PerApiRequest is not throwing an AutoFac resolution exception is useful to know as it shows that all dependents on the Cache are infact scoped by the request. Any singletons or dependents outside of the request scope would be getting a resolution exception of some kind. – Matt Caton Jul 21 '14 at 21:59
  • Just to confirm with your code - is the Cache responsible for creating the connection? Is this first constructed in your controller and does your controller in anyway reference an ISqlServerConnectionContext dependency? Could you put a little more code up around these two areas? – Matt Caton Jul 21 '14 at 22:03
  • Yes, the cache is creating the connection and setting the ```CurrentConnectionContext```. What appears to be happening is Autofac is running the delegate that registers ```ISqlServerConnectionContext``` _before_ executing the controller which is where the factory (and the cache) gets created. – Colin Young Jul 22 '14 at 12:52
  • I think at least one of the issues is that I have ```DocumentController -> IConnectionContextFactory -> IConnectionContextCache```. ```IConnectionContextCache``` returns ```IConnectionContext```. I have other components that depend on ```ISqlServerConnectionContext -> IConnectionContext``` but Autofac doesn't know that the ```DocumentSearch``` controller needs to be run before ```IConnectionContextCache.CurrentConnectionContext``` will be initialized. However, if I attempt to resolve ```Document Controller``` in the delegate, I get a circular reference error. – Colin Young Jul 22 '14 at 14:48

1 Answers1

0

This can be solved using AutoFac's support for object graph lifetime scoping.

  1. Cache the current SqlServerConnectionContext in an object scoped to the lifetime of your controller.
  2. Within the SqlServerConnectionContext factory type, once the connection is created assign it to the backing field of the current lifetime-scoped cache
  3. Any types scoped within the lifetimes scope of a controller can then access the connection associated with that controller through the cache

The only complexities I can think of are:

  1. If the controller is not actually the root of a lifetime scope for all types with a dependency on a specific connection. I.e. if they fall outside the lifetime of the controller.
  2. If any of the dependencies are registered as single instance. In which case they will not be able to resolve the Cache as it is currently implemented as it is PerApiRequest.

For example:

public interface ISqlServerConnectionContextCache
{
    ISqlServerConnectionContext CurrentContext { get; set; }
}

public class SqlServerConnectionContextScopeCache : ISqlServerConnectionContextCache
{
    public ISqlServerConnectionContext CurrentContext { get; set; }
}

public interface ISqlServerConnectionContextFactory
{
  ISqlServerConnectionContext Create();
}

// The factory has the cache as a dependancy
// This will be the first use of the cache and hence
// AutoFac will create a new one at the scope of the controller
public class SqlServerConnectionContextFactory : ISqlServerConnectionContextFactory
{
  private string _connectionString;
  private ISqlServerConnectionContextCache _connectionCache;

  public SqlServerConnectionContextFactory(ISqlServerConnectionContextCache connectionCache,
    string connectionString)
  {
    _connectionCache = connectionCache;
    _connectionString = connectionString;  
  }

  public ISqlServerConnectionContext Create()
  {
    var connectionContext = new SqlServerConnectionContext(_connectionString);
    connectionContext.Open();
    _sqlServerConnectionContextProvider.CurrentContext = connectionContext;
    return connectionContext;
  }
}

public class MyController : ApiController
{
  private ISqlServerConnectionContext _sqlServerConnectionContext;

  public MyController(Func<string, ISqlServerConnectionContextFactory> connectionFactory)
  {
    _sqlServerConnectionContext = connectionFactory("MyConnectionString");
  }
}

// As the cache is lifetime scoped it will receive the single instance
// of the cache associated with the current lifetime scope
// Assuming we are within the scope of the controller this will receive
// the cache that was initiated by the factory
public class MyTypeScopedByController
{
    public MyTypeScopedByController(ISqlServerConnectionContextCache connectionCache)
    {
        var sqlServerConnectionContext = connectionCache.CurrentContext;
    }
}

// AutoFac wiring
builder.RegisterType<SqlServerConnectionContextScopeCache>()
    .As<ISqlServerConnectionContextCache>()
    .InstancePerApiRequest();
builder.RegisterType<SqlServerConnectionContextFactory>()
    .As<ISqlServerConnectionContextFactory>()
    .InstancePerDependency();
Matt Caton
  • 3,473
  • 23
  • 25
  • That looks like it's going to get me going in the right direction. I don't think I'll need to worry about things being outside of the lifetime scope of the controller. If there are any, it likely will indicate an error in configuring them. – Colin Young Jul 18 '14 at 19:48
  • Should work fine - AutoFac lifetime scoping is a joy to use. We use it for all sorts of things - my favourite being providing a logging context within a business transaction. Any problems just report back and I'll take a look. – Matt Caton Jul 18 '14 at 19:50
  • I've added some more details to the question. I think I do have some non-controller rooted objects, but it's possible I've just messed things up some other way. – Colin Young Jul 21 '14 at 16:08
  • Theres a bug in my answer (now fixed) where the Cache was being registered as PerLifetimeScope. Within the context of a web request, this actually scopes the dependency to the **root** scope and hence is a singleton. Looking at your updated question, I see you are also using PerLifetimeScope - which would definitely cause issues. I also wonder if you have set this because PerApiRequest didn't work? If so this is a sure sign some of the types with a dependency on the cache are single instance. I've updated my answer to include this as well. – Matt Caton Jul 21 '14 at 20:16
  • What I seem to be having trouble with is (in your example) wiring up MyScopedByController, although in my case I've got a lot of classes I can't (easily) modify that depend on ISqlServerConnectionContext and I'm trying to register that by a delegate. i.e. how do I do it "within the scope of the controller"? – Colin Young Jul 21 '14 at 20:29
  • I've updated the question to reflect the fact that InstancePerApiRequest is not working either. – Colin Young Jul 21 '14 at 20:35
  • How about using a service locator type approach to resolve the cache from types that you can't use di on? Maybe even just a GetCurrent static method on the cache and get the cache to use autofac to resolve itself in the right scope? – Matt Caton Jul 21 '14 at 22:40
  • The problem isn't that I can't use DI on some types, it's that those types are expecting a connection context to be injected, not a cache, and since they are in a shared library also used in other projects that are not being converted to Autofac, I can't very well go and modify their interfaces. – Colin Young Jul 22 '14 at 12:26
  • I'm marking this as the answer since it does solve my original problem. The circular reference issue I'm now dealing with is, I believe, a completely separate issue. – Colin Young Jul 23 '14 at 14:36
  • Sorry been really busy so not had a chance to look any further. Good luck finding the root cause. – Matt Caton Jul 23 '14 at 14:37