Closed rastrac-joel closed 1 year ago
Seems as though the registrations were binding constructor arguments to a different name than specified by the constructor. I guess StructureMap must have automatically associated them if there was only one argument of the argument type. I don't think this is mentioned in the related documentation I had read.
Still, attempting to bind to an argument that isn't there seems like a common error that should be accounted for. It's obvious enough when writing new code, but when trying to convert and you have hundreds or thousands of lines of registration code, it's a lot less clear which ones are the problems. I wasn't aware that the constructor argument was the string I was looking at in the exception message since the exception was being thrown in internal code. The name happened to be one that a library might have.
@rastrac-joel Sorry, but I don't understand from this explanation what you're not able to do. Can you put some kind of concrete code in here that demonstrates your problem? For IoC usage, and I know this doesn't help, but I strongly recommend against using types with optional arguments in constructor functions being used with Lamar because of exactly this kind of potential confusion.
There's just nothing actionable here, I'm closing this unless someone can add more concrete information about what the exact problem is.
I found that the problem was StructureMap was permissive of the case where you might have something like
class SomeService : ISomeService
// ...
public SomeService(string configurationString)
// ...
class SomeRegistry : Registry
// ...
For<ISomeService>().Use<SomeService>().Ctor<string>("connectionString").Is(connectionString).Singleton();
// ...
This worked in StructureMap (probably shouldn't have) and Lamar is not permissive of it which is perfectly fine behavior.
The specific problem that I had is that the code in Lamar indicates no helpful context with its exception for the null
that goes along with failing to register a proper constructor argument. All that ends up in the exception is the parameter name of the constructor argument. In the case of the code I was updating to Lamar, because this worked in StructureMap, there were quite a few equivalents of ISomeService
implementations that had the same kind of registration-parameter name disconnect, so the only way I could find them "easily" was taking the Lamar projects and putting them into my solution to be able to walk up the stack to the point where the useful context information was present. The exception message only includes what the parameter name is, not what other things might be registered to the constructor (or the type). ISomeService
implementations were in packages so there was no way for me to easily determine what was affected once I had figured out what was happening and it was very confusing up until the point where I did find out what was happening. If the type had been present in the exception message, it would have been fairly clear upon investigating the specific type.
Converting from StructureMap to Lamar means that there might be a lot of types that fall under this criteria, so at a minimum, the exception carrying more information (in particular, the concrete type) is preferable. This is fairly concrete and actionable. I would think upgrading from StructureMap to Lamar would be something that would be desired or expected from consumers of StructureMap and it would still benefit development using Lamar even if StructureMap is removed from the equation.
I was investigating Lamar's code to attempt to discern what might be the specific problem and found that it's written in a way seemingly confused about its own invariants. I think it would be of benefit to review that as well but since I don't really understand what went into the code being the way it is, there's not much I can provide there except a summary of the things I found.
Okay, now I got you, and that's a happy, unintentional "feature" of StructureMap. I even know the loosey goosey code in SM that did the inline matching like that. The "easy" way going forward IMO is to not use primitive/simple types in the container. Even in the SM days, I've recommended using strong typed "Setting" or now .NET "Options" classes instead of trying to rely on named constructor values for connection strings, file paths, etc.. That's my 2 cents.
If you can code a repo, I can do something to attempt a better exception message. TBH, I really just want to strongly recommend not using simple types as dependencies period. Or use a lambda registration that pulls the connection string out of IConfiguration as an alternative approach.
There's some code in
ConstructorInstance.cs
:The
Where
clause will never filter out any values becauseCtorArg
prevents any.Instance
from beingnull
:findInstanceForConstructorParameter
appears to be intended to never returnnull
, however,ServiceGraph.FindInstance
will returnnull
under some circumstances and this is accounted for on theparameter.DefaultValue == null
code branch but not outside ofparameter.IsOptional
:ServiceGraph.FindInstance
will callServiceFamily.InstanceFor
which returnsnull
in the case where there is no match for the name in theInstances
collection and there are noInstance
values in the collection that share the name.Additionally,
ServiceGraph.FindDefault
will returnnull
in the case where the type matches theType.IsSimple
extension method or theInstance
returned isnull
or theInstance.Default
field isnull
. I'm not sure from the code if it can return anull
Instance
, but it seems from the usage ofLastOrDefault
thatInstance.Default
could benull
.All of these cases will cause an exception to be thrown from
CtorArg
and it contains no context information that allows the user to figure out where or even what the problem is. All that's known is the constructor argument name and sinceInstance
is attempting to be set tonull
, there's no type information. The constructor argument name could be shared among tens or hundreds of classes. In the exception I'm getting, the constructor argument isn't a defaulted argument in any of the applicable classes either (and I gather from other issues that this could be a problem).The context here is that I'm attempting to update a library that uses StructureMap to using Lamar (specifically for ASP.NET Core on .NET 6 if that makes a difference) and it's not going well and the library does not specify enough information here to understand the problems. I'm sure I'm doing something incorrectly - or maybe the code was doing something that StructureMap was permissive of but Lamar is not. In any case, what it is that I'm doing incorrectly and how to go about fixing it is a different issue. I tried searching for the error messages on multiple search engines and they came up with very little.
Regardless, the invariants that are assumed by the code are sending mixed messages. I don't know enough about the library to suggest any particular improvements, only to raise visibility that this is causing issues and the code itself seems internally confused.