Search code examples
c#dependency-injectiondecoratorscrutor

Register Open Generic Decorator with Scrutor


I have a standard .Net core Api and want to use a Open Generic IReposistory and decorate that with a DomainEventPublisher for pushing out events to servicsBus after persisting. However, I have used Simple Injector a lot earlier which I'm a big fan of. But now when using MediatR Im trying to simplify DI by using just .net Core DI together with Scrutor package for decorating.

Problem is an error I get: "The number of generic arguments provided doesn't equal the arity of the generic type definition." from Scrutor when trying to register decorator in Startup (2nd line below).

services.AddSingleton(typeof(IRepository<>), typeof(Repository<>));
services.Decorate(typeof(IRepository<>), typeof(DomainEventPublisher<>));

I have closed these generic classes/interfaces and then it works. But Im not good with that. I would to i the right way like I used to do in Simpleinjector, and register open generic decorator.

Any suggestions what might be the problem?

 public class Repository<TEntity> : IRepository<TEntity>
{
    private readonly CosmosClient _client;
    private readonly IDataContext<TEntity> _context;
    private readonly Container _container;

    public Repository(CosmosClient client, IDataContext<TEntity> context)
    {
        _client = client;
        _context = context ?? throw new ArgumentNullException(nameof(context));
        _container = _client.GetContainer(_context.GetDatabase(), _context.GetContainer());
    }


    public virtual async Task Add(TEntity entity)
    {
        try
        {
            var response = await _container.CreateItemAsync(entity, new PartitionKey(_context.GetPartitionKey(entity)));
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
      
    }

    public virtual async Task<TEntity> Get(string id)
    {
        var response = await _container.ReadItemAsync<TEntity>(id, new PartitionKey(_context.GetPartitionKey(id)));

        return response.Resource;
    }

    public virtual async Task<TEntity> Update(TEntity entity)
    {
        var response = await _container.UpsertItemAsync(entity, new PartitionKey(_context.GetPartitionKey(entity)));
        
        return response.Resource;
    }

    public async Task Remove(string id)
    {
        var response = await _container.DeleteItemAsync<TEntity>(id, new PartitionKey(_context.GetPartitionKey(id)));
    }

public class DomainEventPublisher<TEntity> : IRepository<TEntity>
{
    private readonly IRepository<TEntity> _decoratedRepository;
    private readonly ITopicAdapter _bus;
    private readonly IMapper _mapper;

    private List<IDomainEvent> _eventsToProcess = new List<IDomainEvent>();

    public DomainEventPublisher(IRepository<TEntity> decoratedRepository, ITopicAdapter bus, IMapper mapper)
    {
        _decoratedRepository = decoratedRepository ?? throw new ArgumentNullException(nameof(decoratedRepository));
        _bus = bus ?? throw new ArgumentNullException(nameof(bus));
        _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
    }
    public async Task Add(TEntity entity)
    {
        // Get all domain events raised by source entity
        var events = CollectEvents(entity);
        await _decoratedRepository.Add(entity);
        await HandleEvents(events);
    }

    public async Task<TEntity> Get(string id)
    {
        return await _decoratedRepository.Get(id);
    }

    public async Task<TEntity> Update(TEntity entity)
    {
        // Get all domain events raised by source entity
        var events = CollectEvents(entity);
        var result = await _decoratedRepository.Update(entity);
        await HandleEvents(events);

        return result;
    }

    public async Task Remove(string id)
    {
        await _decoratedRepository.Remove(id);
    }

    private List<IDomainEvent> CollectEvents(TEntity entity)
    {
        if (entity is IEntity entityWithEvents)
            return entityWithEvents.Events;

        return new List<IDomainEvent>();
    }

    private async Task HandleEvents(List<IDomainEvent> events)
    {
        // if we ended up on this line we know that repository persisted changes and now send events to bus
        foreach (var domainEvent in events)
        {
            await _bus.Send(_mapper.MapTo(domainEvent));
        }
    }
}

Solution

  • It's impossible to apply decorators to open-generic registration with Scrutor. This is discussed here on the Scrutor forum. This is due to a limitation of the underlying Microsoft DI Container. This is a limitation that can't be circumvented by Scrutor.

    Instead, switch to one of the mature DI Containers that do support this.