ReactiveUI Dependency Injection Constructor
Asked Answered
A

3

5

I am using the built in dependency injector/splat injector inside ReactiveUI.

I have constructors where I want to pass along their applicable data repository.

In other frameworks it just uses reflections with the interfaces, and uses the GetServices call to fulfill those constructor requirements. Eg at the moment I have this helper extension method for creating my class:

    /// <summary>
    /// Helper class for having a object's constructor automatically assigned by a "GetService" request.
    /// </summary>
    /// <param name="resolver">The resolver.</param>
    /// <param name="type">The type to register.</param>
    public static void Register<TConcrete, TInterface>(this IMutableDependencyResolver resolver)
        where TConcrete : class
    {
        var concreteType = typeof(TConcrete);

        // Must be a single constructor
        var constructors = concreteType.GetConstructors().Single();

        IList<object> values = new List<object>();

        foreach (var parameter in constructors.GetParameters())
        {
            if (parameter.ParameterType.IsInterface == false)
            {
                throw new InvalidOperationException($"The type {concreteType.Name} has constructor paramters that are not interfaces.");
            }

            values.Add(resolver.GetService(parameter.ParameterType));
        }

        resolver.Register(() => Activator.CreateInstance(concreteType, values.ToArray()), typeof(TInterface));
    }

The reason why I use that helper class is to avoid having to do the following in my AppBootStrapper:

        dependencyResolver.Register(() => new SetupFlightViewModel(dependencyResolver.GetService<IScreen>(), dependencyResolver.GetService<IFlightsModel>(), dependencyResolver.GetService<IAirportsModel>()), typeof(ISetupFlightViewModel));

Just checking to make sure I am not doing anything non-obvious with the Splat/ReactiveUI framework and it's already provided. I realise there is a performance cost of doing the above and could probably use the Expression Tree compiled expressions to avoid the reflection cost every time or something.

Thanks for your help, Glenn

Agra answered 18/9, 2015 at 8:32 Comment(0)
M
5

Splat dependency resolver (service registry) is very basic out of the box, and it doesn't provide dependency injection (DI).

But if you like DI (not everyone does, as it can hide dependencies and design complexity, some ppl prefer to feel the pain of that to better avoid it), you can plug DI easily into it, as you just did.

One comment on your impl though, I'd suggest you delay the actual call to GetService up until the object is created (in case your service registry content changes over time, and to avoid forcing Register ordering), e.g:

var paramType = parameter.ParameterType;
values.Add(() => resolver.GetService(paramType));


... Activator.CreateInstance(concreteType, values.Select(cb => cb()).ToArray()) ...
Metallography answered 18/9, 2015 at 9:33 Comment(2)
The delayed resolution is definitely better. Thanks for the tip.Agra
Just for completeness, what type of container did you use for the delayed lambdas? I've went with IList<Func<object>> values = new List<Func<object>>(); but wondered if there was a better choice?Enunciation
A
2

I wrote a official NuGet package for Splat that does constructor injection.

It's called Splat.DependencyInjection.SourceGenerator.

It uses source generation to find the correct dependencies for a class and inject them into the constructor. Also supports property injection.

https://github.com/reactivemarbles/Splat.DI.SourceGenerator

Agra answered 8/8, 2021 at 3:40 Comment(0)
Q
0

I just started with ReactiveUI and was trying to do the same thing. I found this post on StackOverflow and grabbed the solution. After fleshing it out a bit, I thought I'd share my code. Since this is the first result that popped up in my searches, I thought I'd post the result here so anybody following the same trail can benefit from the fruits of my labor.

public static class Bootstrapper
{
    public static void RegisterDependencies()
    {
        Locator.CurrentMutable.RegisterLazySingleton<ISettingsService>(CreateWithConstructorInjection<InMemorySettingsService>);
        Locator.CurrentMutable.Register<MainWindowViewModel>(CreateWithConstructorInjection<MainWindowViewModel>);
    }

    private static T CreateWithConstructorInjection<T>() where T : class
    {
        // Must be at most one constructor
        ConstructorInfo[] constructors = typeof(T).GetConstructors();
        if (constructors.Count() > 1)
            throw new InvalidOperationException($"Unable to create required dependency for {typeof(T).FullName}: type can not have more than one constructor, found {constructors.Count()}");

        // must not be null
        IEnumerable<Type> types = constructors.Single().GetParameters().Select(p => p.ParameterType).ToArray();
        if (Activator.CreateInstance(typeof(T), types.Select(GetService).ToArray()) is T t)
            return t;

        throw new InvalidOperationException($"Unable to create required dependency of type {typeof(T).FullName}: Activator.CreateInstance() returned null");
    }

    private static object GetService(Type type)
    {
        if (Locator.Current.GetService(type) is Object obj)
            return obj;

        throw new InvalidOperationException($"Unable to create required dependency of type {type.FullName}: IReadonlyDependencyResolver.GetService() returned null");
    }
}
Qulllon answered 6/8, 2021 at 13:56 Comment(2)
I would go with Splat.DependencyInjection.SourceGenerator nuget package myself which doesn't use reflection at runtime. github.com/reactivemarbles/Splat.DI.SourceGeneratorAgra
oooh, nice. I've got to check that out!Qulllon

© 2022 - 2024 — McMap. All rights reserved.