I'm trying to invoke the RegisterType method in the Unity container. RegisterType has a total of 16 overrides (some of those are parameters some are types).
I'm trying to perform the equivalent of:
Container.RegisterType<IMyDataProvider, MockData.MockProvider>("MockData", new ContainerControlledLifetimeManager())
Using GetMethod() was a total failure, so I ended up doing this ugly thing:
MethodInfo registerTypeGeneric = Container.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).
Where(p => p.ToString() == "Microsoft.Practices.Unity.IUnityContainer RegisterType[TFrom,TTo](System.String, Microsoft.Practices.Unity.LifetimeManager, Microsoft.Practices.Unity.InjectionMember[])").FirstOrDefault();
MethodInfo registerTypeSpecific = registerTypeGeneric.MakeGenericMethod( new Type[] { typeof(IMyDataProvider), Assembly.LoadFrom("MockData.dll").GetType("MockData.MockProvider") });
registerTypeSpecific.Invoke(Container, new object[] { "MockData", new ContainerControlledLifetimeManager() });
And this works beautifully, up until the Invoke which complains because I have no InjectionMember parameters (they're optional and I don't have any to give). So, according to the documentation, I have to use Type.InvokeMember() to call a method with optional parameters.
So I did this:
Binder binder = new BootstrapperBinder();
Container.GetType().InvokeMember("RegisterType",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.OptionalParamBinding | BindingFlags.InvokeMethod,
binder,
Container,
new object[] { "MockData", new ContainerControlledLifetimeManager() });
My BoostrapperBinder class does this:
public override MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] names, out object state)
{
Type mockProvider = Assembly.LoadFrom("MockData.dll").GetType("MockData.MockProvider");
state = new object();
MethodInfo mi = Container.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).
Where(p => p.ToString() == "Microsoft.Practices.Unity.IUnityContainer RegisterType[TFrom,TTo](System.String, Microsoft.Practices.Unity.LifetimeManager, Microsoft.Practices.Unity.InjectionMember[])").FirstOrDefault();
return mi.MakeGenericMethod(new Type[] { typeof(ICarrierApprovalDataChangeAccessorEndPoint), mockProvider });
}
Yes, it's ugly, but I just use it for this oen case, so it does the job.
Now, the problem is, it's still complaining about the lack of a third parameter. I can't pass null or Missing.Value either, or it croaks. I've tried with and without BindingFlags.OptionalParamBinding. I'm stumped.
(Edited to put the Container.RegisterType example in code)
I can't pass
null
orMissing.Value
either, or it croaks.
Croaks how? You should be able to pass null
for a params
parameter (when you invoke a method like M(params object[] objects)
via M()
it will be the case that objects
is null within the method.
Second, you can lookup the method more cleanly. I don't have a compiler at my fingertips, but try this:
var registerTypeMethodInfo =
typeof(IUnityContainer).GetMethods()
.Where(m => m.Name == "RegisterType")
.Where(m => m.GetParameters()
.Select(p => p.ParameterType)
.SequenceEqual(new[] {
typeof(string),
typeof(LifetimeManager),
typeof(InjectionMember[])
})
)
.Where(m => m.GetGenericArguments().Count() == 2)
.SingleOrDefault();
Assert.NotNull(registerTypeMethodInfo);
var methodInfo =
registerTypeMethodInfo.MakeGenericMethod(new[] {
typeof(IMyDataProvider),
typeof(MockData.MockProvider)
});
Then invoke the method like so, passing null
for the params InjectionMember[]
parameter:
methodInfo.Invoke(
Container,
new object[] {
"MockData",
new ContainerControlledLifetimeManager(),
null
}
);
Sorry if it doesn't compile. If it doesn't compile, this will get you very close to a correct solution.
Here is a self-contained example that works:
namespace ParamsTest {
interface Foo {
void M<T>(string s, int n, params object[] objects);
}
class Bar : Foo {
public void M<T>(string s, int n, params object[] objects) {
Console.WriteLine(s);
Console.WriteLine(n);
Console.WriteLine(objects == null);
Console.WriteLine(typeof(T).Name);
}
}
internal class Program {
internal static void Main(string[] args) {
var genericMethodInfo =
typeof(Foo).GetMethods()
.Where(m => m.Name == "M")
.Where(m => m.GetParameters()
.Select(p => p.ParameterType)
.SequenceEqual(new[] {
typeof(string),
typeof(int),
typeof(object[])
})
)
.Where(m => m.GetGenericArguments().Count() == 1)
.SingleOrDefault();
var methodInfo =
genericMethodInfo.MakeGenericMethod(
new[] { typeof(DateTime) }
);
var bar = new Bar();
methodInfo.Invoke(bar, new object[] { "Hello, world!", 17, null });
}
}
}
This prints:
Hello, world!
17
True
DateTime
on the console.
I've tried with and without BindingFlags.OptionalParamBinding. I'm stumped.
params
is not part of the signature of a method. It is a compiler trick to allow variable-length parameter lists. BindingFlags.OptionalParamBinding
is for binding optional parameters to their default values.
My initial post mentioned that I had tried passing null as a 3rd parameter and that the app "croaked." Specifically, it was getting a null reference exception and I should have been more clear about that.
The solution was to pass "new InjectionMember[0]" instead of null, so the Invoke() should have looked like this:
registerTypeSpecific.Invoke(Container, new object[] { "MockData", new ContainerControlledLifetimeManager(), new InjectionMember[0] });
Thanks to Jason for his help. His sample sent me down the path that eventually led to the answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With