I have these classes:
Public Class ProcessRunner
Implements IProcessRunner
Public Sub New(Command As ICommand)
End Sub
End Class
Public Class Command
Implements ICommand
Public Sub New(BootStrapper As IBootStrapper)
End Sub
End Class
Public Class BootStrapper
Implements IBootStrapper
Public Sub New(Releases As IReleases)
End Sub
End Class
Public Class Releases
Implements IReleases
Public Sub New(Repository As IRepository)
End Sub
End Class
Public Class Repository
Implements IRepository
Public Sub New(Context As DbContext)
End Sub
End Class
Public Class MsSqlContext
Inherits DbContext
End Class
Public Class SqliteContext
Inherits DbContext
End Class
I'm registering the first ones like so:
Builder.RegisterType(Of ProcessRunner).As(Of IProcessRunner)
Builder.RegisterType(Of BootStrapper).As(Of IBootStrapper)
Builder.RegisterType(Of Releases).As(Of IReleases)
Builder.RegisterType(Of Command).As(Of ICommand)
...and then I'm registering Respository
and MsSqlContext
/SqliteContext
like so:
Dim oSqliteParameter As ResolvedParameter
Dim oSqliteAccessor As Func(Of ParameterInfo, IComponentContext, Object)
Dim oMsSqlParameter As ResolvedParameter
Dim oMsSqlAccessor As Func(Of ParameterInfo, IComponentContext, Object)
Dim oSqlPredicate As Func(Of ParameterInfo, IComponentContext, Boolean)
oSqliteAccessor = Function(Info, Context) Context.ResolveKeyed(Of DbContext)(NameOf(SqliteContext))
oMsSqlAccessor = Function(Info, Context) Context.ResolveKeyed(Of DbContext)(NameOf(MsSqlContext))
oSqlPredicate = Function(Info, Context) Info.ParameterType = GetType(DbContext)
oSqliteParameter = New ResolvedParameter(oSqlPredicate, oSqliteAccessor)
oMsSqlParameter = New ResolvedParameter(oSqlPredicate, oMsSqlAccessor)
With Builder
.RegisterType(Of Repository(Of Dto.Release, Model.Release)).
WithParameter(oSqliteParameter).
Keyed(Of IRepository(Of Dto.Release, Model.Release))(NameOf(SqliteContext)).
As(Of IRepository(Of Dto.Release, Model.Release))()
.RegisterType(Of Repository(Of Dto.Release, Model.Release)).
WithParameter(oMsSqlParameter).
Keyed(Of IRepository(Of Dto.Release, Model.Release))(NameOf(MsSqlContext)).
As(Of IRepository(Of Dto.Release, Model.Release))()
.RegisterType(Of SqliteContext).
InstancePerLifetimeScope.
Keyed(Of DbContext)(NameOf(SqliteContext)).
As(Of DbContext)()
.RegisterType(Of MsSqlContext).
InstancePerLifetimeScope.
Keyed(Of DbContext)(NameOf(MsSqlContext)).
As(Of DbContext)()
End With
The problem is that when I resolve ProcessRunner
, MsSqlContext
is always sent to Repository
internally, presumably because it comes first alphabetically. And my tests are failing, because I end up testing against the production database.
I found this answer, but it doesn't seem to be applicable. For starters, there's no deep dependency chain like there is here. Must I really register two instances of everything all the way down? Isn't there a cleaner way to pass the context name down through the hierarchy?
I consulted ChatGPT (of course!) but it returned some mangled non-compiling code vaguely resembling an implementation of IRegistrationSource
. I looked at the docs for that, but I'm not seeing a good fit. (Correct me if I'm mistaken with that assessment.)
How can I best register and resolve everything in a way that controls which Respository
gets sent to the Releases
constructor when I resolve ProcessRunner
, given that I don't know which Respository
to use until resolution time (test vs. prod)?
It'll likely end up being some way of walking NameOf(SqliteContext)
or NameOf(MsSqlContext)
down the stack, but I'm unsure of how to do that.
(Note: an answer in either VB.NET or C# will be fine.)
--EDIT--
It's a kludge, to be sure, but I was able to at least get it working using a convoluted series of extension methods:
Friend Module Extensions
<Extension>
Friend Function ProcessRunner(Of TContext As DbContext)(Instance As ILifetimeScope) As IProcessRunner
Return Instance.Resolve(Of IProcessRunner)(Instance.CommandParameter(Of TContext))
End Function
<Extension>
Friend Function Command(Of TContext As DbContext)(Instance As ILifetimeScope) As ICommand
Return Instance.Resolve(Of ICommand)(Instance.BootStrapperParameter(Of TContext))
End Function
<Extension>
Friend Function BootStrapper(Of TContext As DbContext)(Instance As ILifetimeScope) As IBootStrapper
Return Instance.Resolve(Of IBootStrapper)(Instance.ReleasesParameter(Of TContext))
End Function
<Extension>
Friend Function Releases(Of TContext As DbContext)(Instance As ILifetimeScope) As IReleases
Return Instance.Resolve(Of IReleases)(Instance.RepositoryParameter(Of TContext))
End Function
<Extension>
Friend Function Repository(Of TContext As DbContext)(Instance As ILifetimeScope) As IRepository
Return Instance.ResolveKeyed(Of IRepository)(GetType(TContext).Name)
End Function
<Extension>
Private Function RepositoryParameter(Of TContext As DbContext)(Instance As ILifetimeScope) As TypedParameter
Return TypedParameter.From(Instance.Repository(Of TContext))
End Function
<Extension>
Private Function ReleasesParameter(Of TContext As DbContext)(Instance As ILifetimeScope) As TypedParameter
Return TypedParameter.From(Instance.Releases(Of TContext))
End Function
<Extension>
Private Function BootStrapperParameter(Of TContext As DbContext)(Instance As ILifetimeScope) As TypedParameter
Return TypedParameter.From(Instance.BootStrapper(Of TContext))
End Function
<Extension>
Private Function CommandParameter(Of TContext As DbContext)(Instance As ILifetimeScope) As TypedParameter
Return TypedParameter.From(Instance.Command(Of TContext))
End Function
End Module
It's called like this:
<Fact>
Sub Process_Runner_Should_Have_Correct_Arguments()
Dim oProcessRunner As IProcessRunner
Using oScope As ILifetimeScope = Me.Container.BeginLifetimeScope
' Arrange
' Act
oProcessRunner = oScope.ProcessRunner(Of SqliteContext)
' Assert
oProcessRunner.Command.Arguments.Update.Count.Should.Be(4)
oProcessRunner.Command.Arguments.Clean.Count.Should.Be(6)
End Using
End Sub
Now tell me if THAT doesn't make you dizzy!
It works, yes, but surely there must be a better way. I'm open to any and all suggestions for improvement.