Search code examples
c#reflectionautofaccommand-pattern

Resolving Generic Interface with Autofac


These are my classes:

   public interface ICommandDtc{
        string Command { get; set; }
        string Xml { get; set; }
    }
    public interface ICommandHandler<in TCommand>
        where TCommand : ICommandDtc
    {
        CommandResult Execute(TCommand command);
        Task<CommandResult> ExecuteAsync(TCommand command);
    }

    public class CommandResult
    {
        public string Description { get; set; }
        public int Code { get; set; }
    }

    public interface ICommandBus{
    Task<CommandResult> SubmitAsync<TCommand>(TCommand command) where TCommand : ICommandDtc;
    CommandResult Submit<TCommand>(TCommand command) where TCommand : ICommandDtc;
    }

    public class CommandBus : ICommandBus{
        private readonly ILifetimeScope _container;
        public CommandBus(ILifetimeScope scope){
            _container = scope;
        }
        public async Task<CommandResult> SubmitAsync<TCommand>(TCommand command)
            where TCommand : ICommandDtc{
            var commandHandler = _container.Resolve<ICommandHandler<TCommand>>();
            return await commandHandler.ExecuteAsync(command);
        }
        public CommandResult Submit<TCommand>(TCommand command)
            where TCommand : ICommandDtc
        {
        **//its worked**
            var commandHandler = _container.Resolve<ICommandHandler<IntegerationCommand>>();
        **//exception**
            var commandHandler2 = _container.Resolve<ICommandHandler<TCommand>>();
            return commandHandler2.Execute(command);
        }
    }

    public abstract class CommandBase<TCommand> : ICommandHandler<TCommand>
        where TCommand : ICommandDtc{
        public async Task<CommandResult> ExecuteAsync(TCommand command){
            var commandResult = new CommandResult();
            try{
                commandResult = await InternalExecuteAsync(command);
            }

            catch (Exception exp){

            }
            return commandResult;
        }
        public CommandResult Execute(TCommand command)
        {
            var commandResult = new CommandResult();
            try
            {
                commandResult =  InternalExecute(command);
            }
            catch (Exception exp)
            {
            }
            return commandResult;
        }

        protected abstract Task<CommandResult> InternalExecuteAsync(TCommand command);
        protected abstract CommandResult InternalExecute(TCommand command);
    }//sample class 1
    public class IntegerationCommandHandler : CommandBase<IntegerationCommand>
    {
        protected override Task<CommandResult> InternalExecuteAsync(IntegerationCommand command){
            throw new System.NotImplementedException();
        }
        protected override CommandResult InternalExecute(IntegerationCommand command){
            switch (command.Command) {
                case "SendDocument":
                    return SendDocument(command.Xml);
            }
            return new CommandResult {Code = 5,Description = ""};
        }
        private CommandResult SendDocument(string xml){
            throw new System.NotImplementedException();
        }
    }//sample class 2
 public class SocialOperationCommandHandler : CommandBase<SocialOperationCommand>
    {
        protected override Task<CommandResult> InternalExecuteAsync(SocialOperationCommand command){
            throw new System.NotImplementedException();
        }
        protected override CommandResult InternalExecute(SocialOperationCommand command){
            throw new System.NotImplementedException();
        }
    }

and my Autofac :

public static IContainer InitializeBusiness()
{
    if (_lifetimeScope != null)
    {
        _lifetimeScope.Dispose();
        _lifetimeScope = null;
    }
    ConfigureAutoMapper();
    var builder = new ContainerBuilder();
    builder.RegisterType<Bootstrapper>().AsSelf();
    var assemblies = Assemblies.GetBusinessAssemblies.ToArray();
    builder.RegisterAssemblyTypes(assemblies).AsImplementedInterfaces();
    builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(ICommandDtc))).Named<ICommandDtc>(x => x.Name);

    builder.RegisterType<AutoFacValidatorFactory>().As<IValidatorFactory>();

    _container = builder.Build();
    return _container;
}

I tried to use :

try
{
    var addFormDtc=new AddFormDtc {CommandName = "SendDocument",SiteCollectionName = "IntegerationCommand",Xml = "1"};
    var obj = _scope.ResolveNamed<ICommandDtc>(addFormDtc.SiteCollectionName);
    obj.Command = addFormDtc.CommandName;
    obj.Xml = addFormDtc.Xml;
    var commandBus = _scope.Resolve<ICommandBus>();
    return commandBus.Submit(obj);
}
catch (Exception ex){
    comandResult.Code = 0;
    comandResult.Description = ex.Message;
    return comandResult;
}

But I'm getting exception in this line :

var commandHandler2 = _container.Resolve<ICommandHandler<TCommand>>();

when i tried manually its worked :

var commandHandler = _container.Resolve<ICommandHandler<IntegerationCommand>>();

exception :

The requested service 'JahadServices.Business.Services.Command.ICommandHandler`1[[JahadServices.Business.Dtos.Command.ICommandDtc, JahadServices.Business.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.


Solution

  • You are trying to resolve ICommandHandler<ICommandDtc>, but you registered only ICommandHandler<IntegerationCommand> from this

     IntegerationCommandHandler : CommandBase<IntegerationCommand>
    

    but ICommandHandler<ICommandDtc> and ICommandHandler<IntegerationCommand> is different types.

    Update:

    I took your original solution and did following: replace this commandBus.Submit(obj); by this

    commandBus.GetType().GetMethod(nameof(ICommandBus.Submit))
        .MakeGenericMethod(obj.GetType())
        .Invoke(commandBus, BindingFlags.Public, null, new[] { obj},
         CultureInfo.CurrentCulture);
    

    And it worked :) Additional info here Calling generic method with a type argument known only at execution time

    Small explanation.

    When you call generic method (Submit), the type inside this method depends of variable pointer type. In your case you stored IntegerationCommand instance inside variable with type ICommandDtc. Soo, when you call Submit(ibj), it was like Submit(ibj). So, this was initial problem, that you called method with wrong generic parameter. I just used reflection to call Submit with right generic parameter (Submit).

    commandBus.GetType()
    .GetMethod(nameof(ICommandBus.Submit)) //<- getting Submit<> method
        .MakeGenericMethod(obj.GetType()) //<- set generic parameter, 
                                          // so now it Submit<IntegerationCommand> 
        .Invoke(commandBus, BindingFlags.Public, null, new[] { obj}, //<- invoke method