Autofac: Decorators unable to use built-in relationships (Lazy, Func) for the decorated service

Sorry, my english is poor. So just show my code.

public interface ICommand<out TResult> { }

interface ICommandHandler<in TCommand, TResult> where TCommand : ICommand<TResult>
{
	Task<TResult> Handle(TCommand command, CancellationToken cancellationToken);
}

class CreateInvoiceCommandHandler : ICommandHandler<CreateInvoiceCommand, CreateInvoiceDTO>
{
	private readonly IInvoiceRepository _invoiceRepository;

	public CreateInvoiceCommandHandler(IInvoiceRepository invoiceRepository)
	{
		_invoiceRepository = invoiceRepository;
	}

	public async Task<CreateInvoiceDTO> Handle(CreateInvoiceCommand command, CancellationToken cancellationToken)
	{
		return new CreateInvoiceDTO();
	}
}

class LazyCommandHandlerDecorator<TCommand, TResult> : ICommandHandler<TCommand, TResult> where TCommand : ICommand<TResult>
{
	private readonly Lazy<ICommandHandler<TCommand, TResult>> _commandHandler;

	public LazyCommandHandlerDecorator(Lazy<ICommandHandler<TCommand, TResult>> commandHandler)
	{
		_commandHandler = commandHandler;
	}

	public async Task<TResult> Handle(TCommand command, CancellationToken cancellationToken)
	{
		return await _commandHandler.Value.Handle(command, cancellationToken);
	}
}

And I use this to register:

public class ApplicationModule : Autofac.Module
{
	protected override void Load(ContainerBuilder builder)
	{
		builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
			.Where(x => !x.IsGenericType)
			.AsClosedTypesOf(typeof(ICommandHandler<,>));

		builder.RegisterGenericDecorator(typeof(LazyCommandHandlerDecorator<,>), typeof(ICommandHandler<,>));
	}
}

When LazyCommandHandlerDecorator is create, the parameter in constructor is LazyCommandHandlerDecorator, i wish it is CreateInvoiceCommandHandler, what mistake did i make?

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 27 (15 by maintainers)

Commits related to this issue

Most upvoted comments

Something I worry about that comes up a lot is special-casing around particular relationship types - the notion of “if we’re not resolving (Func | Lazy | Owned | IEnumerable | IIndex [pick one or more]) then do XYZ, otherwise…” As the framework evolves and we need to look at new relationship types (I’m waiting for something like ValueEnumerable<T> the same as there’s now a ValueTask<T>) having any code from one feature that ties to another feature with special casing seems like it’s ripe for problems.

Not to mention the recent “I registered my own instance of Func<T> and that needs to take precedence over the built-in Func<T>” issue I remember reading.

It’s like there needs to be a flag on the registration that says “this fulfills a special built-in relationship” which may be covered by the IsAdapterForIndividualComponents thing @alexmg just added to the context but I’m not sure. Similar issues for special casing have come up in things like the dynamic proxy support and aggregate service support, so whatever comes out here should ostensibly be something that could be used by extensions.

I’m also sort of weirded out by what the expected behavior of a Func<T> might be in the middle of a decorator chain. There’s no guarantee the decorator isn’t going to call that thing twice, which makes me wonder what the behavior should be. And if there’s a chain of decorators all of which use Func<T> and if all of them potentially call that method more than once because people do some crazy stuff sometimes… maybe I’m overthinking it.

Cool, I’ll get the refactored version and make a PR out of it for completeness - whether that’ll be worth merging or not I’m not sure. Agree that this function superficially doesn’t seem like much of an extension, but on closer inspection is both an edge-case in terms of functionality (compared to alternatives) and does add a decent amount of complexity even just on a conceptual level. I’d suggest that at least it would be worth adding something to the documentation about limitations on classes which can be used for decoration, specifically in regard to constructor requirements and adaption, since it looks like this has come up a couple of times recently.

That said, the solution I’ll submit is mostly just a generalisation of the existing code and when it doesn’t involve deferred resolution, it results in a process which is mechanically the same. It’s only when using Func<> dependencies (which I’ll actually make explicit) that the context breaks down, although it will still return >1 nested contexts which are filled-in at different times. Generally, it might add some flexibility for connected issues in future, so may be worth a look.

I’m not going to make any changes at this point and will think about the scenario for a while. I can see the desire to structure things like this but cannot see how the implementation would work yet.

Turns out this happens with Lazy<T> and Func<T> if you use those in the parameter for the decorator. I’ve updated the tests and the title of the issue to include that.