Register Services (Dependency Injection)

Register Services (Dependency Injection)

jmadeley37KVE
Contributor Contributor
1,132 Views
8 Replies
Message 1 of 9

Register Services (Dependency Injection)

jmadeley37KVE
Contributor
Contributor

Hi all,

 

I'm looking for a way to register services such as a Logger with Revit so that it can be called later on using Dependency Injection.

 

I've previously worked with ASP.NET applications with the usual Startup.cs and IHost but can't seem to find the IServicesCollection through the IExternalApplication interface.

 

Can anyone provide some suggestions?

 

Thanks

Jack

0 Likes
1,133 Views
8 Replies
Replies (8)
Message 2 of 9

mhannonQ65N2
Collaborator
Collaborator

As far as I can tell, there is nothing like that in Revit. If you want some sort of dependency injection container, you'll need to write your own or use a 3rd party library. Revit uses the Castle.Windsor library so if you want to use that you'll need to use the same version that Revit ships with (3.2.1.20 for Revit 2020 and 2023).

0 Likes
Message 3 of 9

jmadeley37KVE
Contributor
Contributor

Thanks!

 

I'll take a look around in that case and see if I can engineer a solution.

 

If I come up with anything I'll be sure to post back here.

0 Likes
Message 4 of 9

Kennan.Chen
Advocate
Advocate

I use a self-implemented DI container to host services in my application and it works completely fine.

Message 5 of 9

jmadeley37KVE
Contributor
Contributor

Hi Kennan,

 

Would you be able to provide a high-level overview of how you have structured your solution?

 

Are you using external libraries or Microsoft's own implementation of dependency injection?

 

Thanks

Jack

0 Likes
Message 6 of 9

Kennan.Chen
Advocate
Advocate

The simple definition of my container, which is an extended interface based on the System.IServiceProvider

 

 

 

/// <summary>
///     Use to manage instances and the creation of instances
/// </summary>
public interface IContainer : IDisposable, IServiceProvider
{
    #region Others

    /// <summary>
    ///     Get an instance of a specific service
    /// </summary>
    /// <typeparam name="TService">The type of the service</typeparam>
    /// <returns>The instance of the service</returns>
    TService GetService<TService>();

    /// <summary>
    ///     Inject the instance properties from the <see cref="IContainer" />
    /// </summary>
    /// <param name="instance">The instance to inject</param>
    void InjectProperties(object instance);

    /// <summary>
    ///     Register the type of the service to the container
    /// </summary>
    /// <typeparam name="TService">The type of the service</typeparam>
    void Register<TService>();

    /// <summary>
    ///     Register the type of the service to the container with the implementation type
    /// </summary>
    /// <typeparam name="TService">The type of the service</typeparam>
    /// <typeparam name="TImpl">The implementation type</typeparam>
    void Register<TService, TImpl>() where TImpl : TService;

    /// <summary>
    ///     Register the type of the service to the container with a delegate to create the instance
    /// </summary>
    /// <typeparam name="TService">The type of the service</typeparam>
    /// <param name="instanceCreator">The delegate to create the service instance</param>
    void Register<TService>(Func<TService> instanceCreator);

    #endregion
}

 

 

 

The simple implementation of IContainer which is a singleton

 

 

 

public class SimpleContainer : IContainer
{
    #region Constructors

    static SimpleContainer()
    {
        Container = new SimpleContainer();
    }

    private SimpleContainer()
    {
        this.Singleton<IContainer>(this);
    }

    #endregion

    #region Properties

    public static IContainer Container { get; }

    private static ConcurrentDictionary<Type, IEnumerable<PropertyInfo>> InjectableProperties { get; } =
        new ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>();

    private Dictionary<Type, Func<object>> Registrations { get; } = new Dictionary<Type, Func<object>>();

    #endregion

    #region Interface Implementations

    public T GetService<T>()
    {
        return (T) GetService(typeof(T));
    }

    /// <summary>
    ///     Inject the properties attributed with InjectAttribute
    /// </summary>
    /// <param name="instance">The instance</param>
    public void InjectProperties(object instance)
    {
        if (null == instance) return;
        GetInjectableProperties(instance)
            .ToList()
            .ForEach(property => { property.SetValue(instance, GetService(property.PropertyType), null); });
    }

    public void Register<TService>()
    {
        Register<TService, TService>();
    }

    public void Register<TService, TImpl>() where TImpl : TService
    {
        InternalRegister<TService>(() =>
        {
            var implType = typeof(TImpl);
            return typeof(TService) == implType
                        ? this.CreateInstance(implType)
                        : GetService(implType);
        });
    }

    public void Register<TService>(Func<TService> instanceCreator)
    {
        InternalRegister<TService>(() => instanceCreator());
    }

    public void Dispose()
    {
        Registrations.Clear();
    }

    public object GetService(Type serviceType)
    {
        if (Registrations.TryGetValue(serviceType, out var creator))
        {
            return creator();
        }

        if (!serviceType.IsAbstract)
        {
            return this.CreateInstance(serviceType);
        }

        throw new Exception($"No registration found for service : {serviceType}");
    }

    #endregion

    #region Others

    private static IEnumerable<PropertyInfo> GetInjectableProperties(object obj)
    {
        var type = obj.GetType();
        var properties = InjectableProperties.GetOrAdd(type,
            t =>
            {
                return t.GetProperties(BindingFlags.Instance
                                        | BindingFlags.SetProperty
                                        | BindingFlags.NonPublic
                                        | BindingFlags.Public)
                        .Where(property =>
                                property.GetCustomAttributes(typeof(InjectAttribute), false).Any())
                        .Where(property => property.GetValue(obj, null) == null).ToList();
            });
        return properties;
    }

    private void InternalRegister<TService>(Func<object> creator)
    {
        var key = typeof(TService);
        if (Registrations.ContainsKey(key))
            Registrations[key] = creator;
        else
            Registrations.Add(typeof(TService), creator);
    }

    #endregion
}

 

 

 

And then I created a base class for all the objects who wants to consume the DI ability of the container

 

 

 

/// <summary>
///     A base object which can automatically inject properties attributed with <see cref="InjectAttribute" /> from the
///     <see cref="IContainer" />
/// </summary>
public abstract class ContainerObject
{
    #region Constructors

    // ReSharper disable once UnusedMember.Global
    protected ContainerObject()
    {
        InjectProperties();
    }

    #endregion

    #region Properties

    protected IContainer Container => SimpleContainer.Container;

    #endregion

    #region Others

    protected void InjectProperties()
    {
        Container?.InjectProperties(this);
    }

    #endregion
}

 

 

 

Of course with the help of InjectAttribute, I can inject service automatically

 

 

 

/// <summary>
///     Inject an object to a property from the <see cref="IContainer" />, the property should always have a setter
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class InjectAttribute : Attribute
{
}

 

 

 

To better register services to the container, I created some extension methods around IContainer

 

 

public static class ContainerExtensions
{
    #region Others

    public static IContainer Singleton<TService>(this IContainer container, TService instance)
    {
        container.Register(() => instance);
        return container;
    }

    public static IContainer Singleton<TService>(this IContainer container, Func<TService> instanceCreator)
    {
        var lazy = new Lazy<TService>(instanceCreator);
        container.Register(() => lazy.Value);
        return container;
    }

    public static IContainer Singleton<TService, TImpl>(this IContainer container)
        where TImpl : TService
    {
        return container.Singleton<TService, TImpl>(() =>
            typeof(TService) == typeof(TImpl) ? container.CreateInstance<TImpl>() : container.GetService<TImpl>());
    }

    public static IContainer Singleton<TService, TImpl>(this IContainer container, Func<TImpl> instanceCreator)
        where TImpl : TService
    {
        var lazy = new Lazy<TImpl>(instanceCreator);
        container.Register<TService>(() => lazy.Value);
        return container;
    }

    public static IContainer Singleton<TService1, TService2, TImpl>(this IContainer container)
        where TImpl : TService1, TService2
    {
        var implType = typeof(TImpl);
        return container.Singleton<TService1, TService2, TImpl>(() => typeof(TService1) == implType ||
                                                                        typeof(TService2) == implType
                                                                            ? container.CreateInstance<TImpl>()
                                                                            : container.GetService<TImpl>());
    }

    public static IContainer Singleton<TService1, TService2, TImpl>(this IContainer container, Func<TImpl> instanceCreator)
        where TImpl : TService1, TService2
    {
        var lazy = new Lazy<TImpl>(instanceCreator);
        container.Register<TService1>(() => lazy.Value);
        container.Register<TService2>(() => lazy.Value);
        return container;
    }

    public static IContainer Singleton<TService1, TService2, TService3, TImpl>(this IContainer container)
        where TImpl : TService1, TService2, TService3
    {
        var implType = typeof(TImpl);
        return container.Singleton<TService1, TService2, TImpl>(() => typeof(TService1) == implType ||
                                                                        typeof(TService2) == implType ||
                                                                        typeof(TService3) == implType
                                                                            ? container.CreateInstance<TImpl>()
                                                                            : container.GetService<TImpl>());
    }

    public static IContainer Singleton<TService1, TService2, TService3, TImpl>(this IContainer container, Func<TImpl> instanceCreator)
        where TImpl : TService1, TService2, TService3
    {
        var lazy = new Lazy<TImpl>(instanceCreator);
        container.Register<TService1>(() => lazy.Value);
        container.Register<TService2>(() => lazy.Value);
        container.Register<TService3>(() => lazy.Value);
        return container;
    }

    public static IContainer Singleton<TService1, TService2, TService3, TService4, TImpl>(this IContainer container)
        where TImpl : TService1, TService2, TService3, TService4
    {
        var implType = typeof(TImpl);
        return container.Singleton<TService1, TService2, TImpl>(() => typeof(TService1) == implType ||
                                                                        typeof(TService2) == implType ||
                                                                        typeof(TService3) == implType ||
                                                                        typeof(TService4) == implType
                                                                            ? container.CreateInstance<TImpl>()
                                                                            : container.GetService<TImpl>());
    }

    public static IContainer Singleton<TService1, TService2, TService3, TService4, TImpl>(this IContainer container, Func<TImpl> instanceCreator)
        where TImpl : TService1, TService2, TService3, TService4
    {
        var lazy = new Lazy<TImpl>(instanceCreator);
        container.Register<TService1>(() => lazy.Value);
        container.Register<TService2>(() => lazy.Value);
        container.Register<TService3>(() => lazy.Value);
        container.Register<TService4>(() => lazy.Value);
        return container;
    }

    #endregion
}

 

 

And also an extension around System.IServiceProvider to create service instances

 

 

public static class ServiceProviderExtensions
{
    #region Others

    /// <summary>
    ///     Create the instance of the implementation type, inject all the constructor parameters and inject properties
    /// </summary>
    /// <param name="container">The instance of <see cref="IServiceProvider" /></param>
    /// <param name="implementationType"></param>
    /// <returns></returns>
    public static object CreateInstance(this IServiceProvider container, Type implementationType)
    {
        var ctor = implementationType
                    .GetConstructors(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.CreateInstance |
                                    BindingFlags.Instance).OrderByDescending(x => x.GetParameters().Length).First();
        var parameterTypes = ctor.GetParameters().Select(p => p.ParameterType);
        var dependencies   = parameterTypes.Select(container.GetService).ToArray();
        var instance = Activator.CreateInstance(implementationType,
            BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.CreateInstance |
            BindingFlags.Instance, null, dependencies, CultureInfo.CurrentCulture);
        return instance;
    }

    public static T CreateInstance<T>(this IServiceProvider container)
    {
        return (T) container.CreateInstance(typeof(T));
    }

    public static T GetService<T>(this IServiceProvider container)
    {
        var service = container.GetService(typeof(T));
        return (T) service;
    }

    /// <summary>
    ///     Inject all the injectable properties
    /// </summary>
    /// <param name="container"></param>
    /// <param name="instance"></param>
    /// <param name="selector"></param>
    internal static void InjectProperties(this IServiceProvider container, object instance, Func<PropertyInfo, bool> selector)
    {
        if (null == instance) return;
        try
        {
            instance.GetType().GetProperties().Where(selector).ToList()
                    .ForEach(property =>
                            property.SetValue(instance, container.GetService(property.PropertyType), null));
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
    }

    #endregion
}

 

 

 

Now I can write a service and register it on App starts up.

 

 

public interface IMyServiceA
{
  void Method();
}

public class MyServiceA: IMyServiceA
{
  void Method()
  {

  }
}

public class App: ContainerObject, IExternalApplication
{
  public Result OnShutdown(UIControlledApplication application)
  {
      try
      {
        // do some cleaning
        return Result.Succeeded;
      }
      catch (Exception e)
      {
          return Result.Failed;
      }
  }

  public Result OnStartup(UIControlledApplication application)
  {
      try
      {
        this.Container.Singleton<IMyServiceA, MyServiceA>()
      }
      catch (Exception e)
      {
          MessageBox.Show(e.ToString(), @"Oops!");
      }

      return Result.Succeeded;
  }
}

 

 

 

In another service I can consume a registered service like this(property injection and constructor injection)

 

 

public interface IMyServiceB
{
  void Method();
}

// property injection
public class MyServiceB: ContainerObject, IMyServiceB
{
  [Inject]
  private IMyServiceA ServiceA{ get; set; }
  void Method()
  {
    this.ServiceA.Method()
  }
}

public interface IMyServiceC
{
  void Method();
}

// constructor injection
// MyServiceC is better to be registered to the Container to get created automatically with an instance of IMyServiceA
public class MyServiceC: ContainerObject, IMyServiceC
{
  public MyServiceC(IMyServiceA serviceA)
  {
    this.ServiceA = serviceA;
  }
  private IMyServiceA ServiceA{ get; set; }
  void Method()
  {
    this.ServiceA.Method()
  }
}

 

 

 

It is just a basic design of my DI system with detail reduced. Hope it helps

Message 7 of 9

ricaun
Advisor
Advisor

Really cool implementation, maybe a simple library for that could be cool.

 

The only DI for Revit I know is this framework: https://github.com/engthiago/Onboxframework/

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 8 of 9

Kennan.Chen
Advocate
Advocate

I created this code years ago for the reason to build the wheels to enhance my technical skills. There are some really awesome open source DI frameworks out there developers can leverage, which makes my DI system look like a toy.😂

 

Moreover, it is part of my company project which means it requires much refactoring or completely redesign before I can publish it legally. Just like I need to reduce much detail before I can publish it here partially. Revit.Async is such a library I extracted from what I've created in my company project but with a completely new design.

0 Likes
Message 9 of 9

ricaun
Advisor
Advisor

Today I created a simple project with your implementation to experiment, kinda add others missing features.

 

Kinda was able to create a generic RevitCommand.

[Transaction(TransactionMode.Manual)]
public class RevitCommand<T> : IContainerObject, IExternalCommand where T : ICommand
{
    public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elementSet)
    {
        this.GetService<T>().Execute();
        return Result.Succeeded;
    }
}
public interface ICommand
{
    void Execute();
}
public class CommandA : ICommand
{
    private readonly UIApplication uiapp;

    public CommandA(UIApplication uiapp)
    {
        this.uiapp = uiapp;
    }
    public void Execute()
    {
        System.Windows.MessageBox.Show($"{this.GetType().Name} - {uiapp}");
    }
}

And create the button using my ricaun.Revit.UI package.

ribbonPanel.CreatePushButton<RevitCommand<CommandA>>();

DI is fun.

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes