Scrutor icon indicating copy to clipboard operation
Scrutor copied to clipboard

Question: Is it possible to do a Decoration for all types assignable from some base type

Open tim-fay opened this issue 7 years ago • 3 comments

Hi,

I have a BaseController that needs dependencies to be injected via Property/Method Injection type. I found it useful to utilize a Decoration mechanics of this library using lambda method.

So my question is this: Is it possible to do a "decoration" for all types that are descendants of some BaseController?

tim-fay avatar Apr 05 '18 19:04 tim-fay

Hmm... I don't think this is possible currently, but it might be doable.

Best case is just changing this line:

https://github.com/khellang/Scrutor/blob/5516fe092594c5063f6ab885890b79b2bf91cc24/src/Scrutor/ServiceCollectionExtensions.Decoration.cs#L296

To

services.Where(service => serviceType.IsAssignableFrom(service.ServiceType))

That could allow you to do

services.Decorate<ControllerBase>(x =>
{
    x.Property = "Yay, it works!";
    return x;
});

But I'm not sure it's that easy. I'll have a look 👀

How are you registering your controllers? Using AddControllersAsServices? 🤔

khellang avatar Apr 06 '18 15:04 khellang

Yes, AddControllersAsServices was my intention. We'd like to use property injection instead of constructor injection in order to avoid repetitive logic with Controller's constructors, having a BaseController type which requires couple services to be injected.

class BaseController : Controller
{
    public BaseController(IFirstService first, ISecondService second)
    {
        FisrtServcie = first;
        SeconService = second;
    }
    public IFirstService FirstService { get; }
    public ISecondService SecondService { get; }
}

class ConcreteController : BaseController
{
    // Repetitive logic that we'd like to avoid via decoration
    public ConcreteController(IFirstService first, ISecondService second)
        : base(first, second)
    { }
}

tim-fay avatar Apr 07 '18 13:04 tim-fay

deleted my prior comment (showing workaround), as here is an improved version, it only decorates serviceTypes that inherit from IInitializeableService, this seems to work well for my use case.


   public static async Task _FixUpInitialableServices(this IServiceCollection services, CancellationToken ct)
   {
      var hasImplementation = services
         .Where(sd => sd.ImplementationType != null || sd.ImplementationInstance != null ||
                      sd.ImplementationFactory != null);

      hasImplementation = hasImplementation.Where(sd =>
      {
         var serviceType = sd.ServiceType;

         if (typeof(IInitializeableService).IsAssignableFrom(serviceType)) return true;

         //can't check factory return type, so try it;
         if (sd.ImplementationFactory != null) return true;

         //check if ImplementationInstance inherits from IInitializeableService
         if (sd.ImplementationInstance != null)
         {
            if (typeof(IInitializeableService).IsAssignableFrom(sd.ImplementationInstance.GetType())) return true;
            return false;
         }

         //check if ImplementationType inherits from IInitializeableService
         if (sd.ImplementationType != null)
         {
            if (typeof(IInitializeableService).IsAssignableFrom(sd.ImplementationType)) return true;
            return false;
         }

         return false;
      });

      var groupedByServiceType = hasImplementation
         .GroupBy(serviceDescriptor => serviceDescriptor.ServiceType);

      var serviceTypes = groupedByServiceType.Select(grouping => grouping.Key).ToList();

      foreach (var serviceType in serviceTypes)
      {
         try
         {
            services.Decorate(serviceType, (innerService, serviceProvider) =>
            {
               if (innerService is IInitializeableService initService)
                  initService.Initialize(ct)._SyncWait();
               return innerService;
            });
         }
         catch (DecorationException ex)
         {
            __.ERROR.Log("error decorating service: ", serviceType, ex);
         }
      }
   }

here is the targeted interface


public interface IInitializeableService
{
 public ValueTask Initialize(CancellationToken ct);
}

notes: my call to _SyncWait() is an extension method. you can just .Wait() the returned value task.

my old version I was trying to Decorate all services, but some are not instantiable so would throw (catchable soft) errors. the above code doesn't have that problem.

jasonswearingen avatar Nov 15 '23 23:11 jasonswearingen