Handler with other interfaces does not receive message


(Mattes337) #1

Hi,

I just ran into a problem with a message handler that also implements other interfaces than IHandleMessages. In my case this is a generic interface IHandleInterval. Whenever a message arrives on the endpoint, it throws an error that no handlers are registered for the specified message although there is one. When removing my own generic interface from the class definition or moving the IHandleMessages to another class everything works fine.

I’m using the nuget package 7.0.0-beta0008 (on net core). Is this a known issue or is there any workaround for now?

Thanks,
Matthias


(Andreas Öhlund) #2

Hi Matthias!

Can you post code for your message handler including the definition of IHandleInterval?

Cheers,

Andreas


(Mattes337) #3

Hi Andreas,

below you find the definitions:

public interface IInterval
{
    int IntervalMs { get; }
}

public interface IHandleInterval
{

}

public interface IHandleInterval : IHandleInterval
where TInterval : IInterval
{
    Task HandleInterval(TInterval interval);
}

public class UserTaskHandler
: IHandleInterval
, IHandleMessages
{

    public async Task HandleInterval(UserTaskInterval interval)
    {
    }

    public async Task Handle(UserTaskExecutedEvent message, IMessageHandlerContext context)
    {
    }
}

Hope it helps,

Thanks!


(Andreas Öhlund) #4

Hi Matthias!

I tried to reproduce this with the following code but it worked like expected on 7.0.0-Beta12.

Can you try it on your end?

using System;
using System.Threading.Tasks;
using NServiceBus;

class Program
{
    static async Task Main(string[] args)
    {
        var endpointConfig = new EndpointConfiguration("GenericInterfaceEndpoint");

        endpointConfig.UseTransport<LearningTransport>();

        var instance = await Endpoint.Start(endpointConfig);

        await instance.SendLocal(new MyMessage());

        Console.ReadKey();
    }
}

public interface IInterval
{
    int IntervalMs  => 2;
}

public interface IHandleInterval
{
}

class UserTaskInterval : IInterval
{
    public int IntervalMs { get; }
}

public interface IHandleInterval<in TInterval> : IHandleInterval
    where TInterval : IInterval
{
    Task HandleInterval(TInterval interval);
}

class HandlerImplementingUnrelatedGenericInterface : IHandleMessages<MyMessage>, IHandleInterval<UserTaskInterval>
{
    public Task HandleInterval(UserTaskInterval interval)
    {
        return Task.CompletedTask;
    }

    public Task Handle(MyMessage message, IMessageHandlerContext context)
    {
        Console.Out.WriteLine("Got the message");
        return Task.CompletedTask;
    }
}

class MyMessage : IMessage
{
}

(Mattes337) #5

Hi Andreas,

it does work in this simple constellation. What I didn’t mention in the first place is that I use Autofac as DI Container instead of the buildin one. When I wire that up I receive an exception that the handler could not be found. When I remove the IHandleInterval interface from the handler it gets fired correctly. One workaround I found is to manually add the handler to the services collection - then it works.

Thanks for your help!
Matthias


(Andreas Öhlund) #6

I tried to install NServiceBus.Autofac 7.0.0-Beta4 and added endpointConfig.UseContainer<AutofacBuilder>(); to the config but the sample still works?

Are there other things I have to change to reproduce this?


(Mattes337) #7

Yes, you have to use a .net core web application. I reduced the code to the bare minimum, see below.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        // Create NServiceBus configuration
        // todo: exchange with proper factory for configured transport, persistence and databus mechanisms
        var exchangeFolder = "D:\\dummyfolder\\";
        var epc = new EndpointConfiguration("GenericInterfaceEndpoint");
        epc.UsePersistence<LearningPersistence>();
        epc.UseSerialization<NewtonsoftSerializer>();

        // Create transport
        var transport = epc.UseTransport<LearningTransport>();
        transport.NoPayloadSizeRestriction();
        transport.StorageDirectory($"{exchangeFolder}MessageBus");

        // Create data bus
        var dataBus = epc.UseDataBus<FileShareDataBus>();
        dataBus.BasePath($"{exchangeFolder}DataBus");

        // Hook up the license data (Base64-Encoded Xml License)
        epc.License("");


        // COMMENT THIS LINE TO INVOKE EXCEPTION
         services.AddTransient<HandlerImplementingUnrelatedGenericInterface>();
        // COMMENT THIS LINE TO INVOKE EXCEPTION

        services.AddSingleton<IHandleInterval<UserTaskInterval>, HandlerImplementingUnrelatedGenericInterface>();
        services.AddSingleton<EndpointConfiguration>(epc);
        services.AddSingleton<IMessageSession>(serviceProvider =>
            Endpoint.Start(serviceProvider.GetService<EndpointConfiguration>()).Result);

        var builder = new ContainerBuilder();
        builder.Populate(services);
        var container = builder.Build();
        epc.UseContainer<AutofacBuilder>(
            customizations: customizations =>
            {
                customizations.ExistingLifetimeScope(container);
            });
        return new AutofacServiceProvider(container);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseMvc();
        app.ApplicationServices.GetService(typeof(IMessageSession));
    }
}

public interface IInterval
{
    int IntervalMs { get; }
}
public interface IHandleInterval
{
}

public interface IHandleInterval<in TInterval> : IHandleInterval
    where TInterval : IInterval
{
    Task HandleInterval(TInterval interval);
}

public class UserTaskInterval : IInterval
{
    public int IntervalMs { get; } = 1000;
}

public class HandlerImplementingUnrelatedGenericInterface
    : IHandleMessages<MyMessage>
    , IHandleInterval<UserTaskInterval>
{
    public Task HandleInterval(UserTaskInterval interval)
    {
        Console.Out.WriteLine("Got the interval");
        return Task.CompletedTask;
    }

    public Task Handle(MyMessage message, IMessageHandlerContext context)
    {
        Console.Out.WriteLine("Got the message");
        return Task.CompletedTask;
    }
}

public class MyMessage : ICommand
{
}

(Andreas Öhlund) #8

Awesome,

Would you be able to zip the entire solution and upload it somewhere so that I can make sure I run the exact same code as you?


(Andreas Öhlund) #9

I was able to repro this and it seems like the offender is

services.AddSingleton<IHandleInterval<UserTaskInterval>, HandlerImplementingUnrelatedGenericInterface>();

That line seems to cause NServiceBus to believe that the handler is already registered but when we try to resolve it using IHandleMessages<MyMessage> it can’t be found.

Since NServiceBus registers all interfaces on the handlers you should not need that registration, can you see if that works?

In the mean time I’ll digg a bit deeper into how autofac treats conflicting registrations like this.

Cheers,

Andreas


(Mattes337) #10

I need this line for my internal interval handling, so I cannot remove it. Adding the specific handlers to the DI resolves this issue, too. For me this workaround is acceptable since I will not be having countless handlers anyway and can do this by reflection.


(Andreas Öhlund) #11

Glad to hear that you got working!