简体   繁体   中英

ReactiveUI Dependency Injection Constructor

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

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), eg:

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


... Activator.CreateInstance(concreteType, values.Select(cb => cb()).ToArray()) ...

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");
    }
}

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM