MassTransit: Problem with integration of MassTransit.SimpleInjector in ASP.NET Core 3.0

Is this a bug report?

Probably. ‘The registered delegate for type ConsumeContext returned null.’ exception will occur during asp.net core 3.0 application starting. This application uses the latest MassTransit.RabbitMq and MassTransit.SimpleInjector NuGet packages.

Can you also reproduce the problem with the latest version?

This problem reproduced on latest version of:

  1. MassTransit.SimpleInjector 5.5.6
  2. MassTransit.RabbitMQ 5.5.6
  3. SimpleInjector.Integration.AspNetCore 4.7.1
  4. SimpleInjector.Integration.AspNetCore.Mvc.Core 4.7.1
  5. ASP.NET Core 3.0

Environment

  1. Operating system: Windows 10(1903), Docker 2.1.0.4(39773), Alpine 3.9
  2. Visual Studio version: 2019
  3. Dotnet version: .Net Core 3.0

Steps to Reproduce

I have created one simple asp.net core application. It acts as a consumer and it acts as producer (for simplify).

  1. Install Docker Desktop (download) (with linux containers)
  2. Checkout project with a reproducible example on your local pc. git clone https://github.com/iVova/AspNetCore3_MassTransit_SimpleInjector.git
  3. Just run the project in Visual Studio

Expected Behavior

  1. The web application should run without any exception and an open browser with the default URL.
  2. SendNotificationOnUserCreatedConsumer consumer should instantiate with IClock application service and receive a message

Actual Behavior

During the starting, web application exception occurred on container verification. (as recommended SimpleInjector)

Exception message:

The configuration is invalid. Creating the instance for type ISendEndpointProvider failed. The registered delegate for type ISendEndpointProvider threw an exception. The registered delegate for type ConsumeContext threw an exception. The registered delegate for type ConsumeContext returned null.

See attached screenshot. MassTransit-SimpleInjector_ConsumerContext_error

Reproducible Demo project

On GitHub: https://github.com/iVova/AspNetCore3_MassTransit_SimpleInjector

Reproducible Demo code

StartUp.cs
using MassTransit;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MyAspCoreApp.Consumers;
using MyAspCoreApp.Services;
using SimpleInjector;
using SimpleInjector.Lifestyles;
using System;

namespace MyAspCoreApp
{
    public class Startup
    {
        private readonly Container _container = new Container();

        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 void ConfigureServices(IServiceCollection services)
        {
            _container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();

            services.AddControllers();

            services.AddSimpleInjector(_container, options =>
            {
                // AddAspNetCore() wraps web requests in a Simple Injector scope.
                options.AddAspNetCore()
                    .AddControllerActivation();
            });

            // !!!
            AddMassTransitThroughSimpleInjector(services, _container);
        }

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

            // !!!!
            ConfigureSimpleInjector(app, _container);

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

        public static IApplicationBuilder ConfigureSimpleInjector(IApplicationBuilder app, Container container)
        {
            // UseSimpleInjector() enables framework services to be injected into
            // application components, resolved by Simple Injector.
            app.UseSimpleInjector(container, options =>
            {
                // Add custom Simple Injector-created middleware to the ASP.NET pipeline.
                // options.UseMiddleware<CustomMiddleware1>(app);

                // Optionally, allow application components to depend on the
                // non-generic Microsoft.Extensions.Logging.ILogger abstraction.
                options.UseLogging();
            });

            InitializeContainer(container);

            // Always verify the container
            container.Verify();

            var busControl = container.GetInstance<IBusControl>();
            busControl.Start();

            return app;
        }

        private static void InitializeContainer(Container container)
        {
            // Add application services. For instance:
            container.Register<IClock, SystemClock>(Lifestyle.Scoped);
        }

        public static void AddMassTransitThroughSimpleInjector(IServiceCollection services, Container container)
        {
            container.AddMassTransit(configurator =>
            {
                configurator.AddConsumersFromNamespaceContaining<SendNotificationOnUserCreatedConsumer>();

                configurator.AddBus(() =>
                {
                    var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
                    {
                        var host = cfg.Host(new Uri("rabbitmq://myaspcoreapp.rabbitMq"), hostConfigurator =>
                        {
                            hostConfigurator.Username("guest");
                            hostConfigurator.Password("guest");
                        });

                        cfg.ConfigureEndpoints(container);
                    });

                    return bus;
                });
            });
        }
    }
}
Producer
using MassTransit;
using Microsoft.AspNetCore.Mvc;
using MyAspCoreApp.Messages;
using System;
using System.Threading.Tasks;

namespace MyAspCoreApp.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private readonly IBus _bus;

        public WeatherForecastController(IBus bus)
        {
            _bus = bus;
        }

        [HttpGet]
        public async Task Get()
        {
            var message = new UserCreatedMessage
            {
                UserId = Guid.NewGuid()
            };

            await _bus.Publish(message);
        }
    }
}
Consumer
using MassTransit;
using MyAspCoreApp.Messages;
using MyAspCoreApp.Services;
using System.Threading.Tasks;

namespace MyAspCoreApp.Consumers
{
    public class SendNotificationOnUserCreatedConsumer : IConsumer<UserCreatedMessage>
    {
        private readonly IClock _clock;

        public SendNotificationOnUserCreatedConsumer(IClock clock)
        {
            _clock = clock;
        }

        public Task Consume(ConsumeContext<UserCreatedMessage> context)
        {
            var time = _clock.UtcNow;
            return Task.CompletedTask;
        }
    }
}

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 5
  • Comments: 19 (7 by maintainers)

Commits related to this issue

Most upvoted comments

Could you please say when can we expect the latest release containing this fix by any chance?

So, this is only an issue because the container validation isn’t creating the scope. MassTransit creates the scope and this error never happens in reality. It’s only in the validation, where there is no scope.

Note that this could be a problem for other DI Containers as well as there are other containers that have this verification feature. Libraries that come to mind are StructureMap, Lamar, but even Microsoft.Extensions.DependencyInjection 3.0 contains this feature. This means that you should be very careful in the registration of these stateful objects to be injected directly.

That change still doesn’t deal with the fact that validate calls without a scope, so the ConsumeContext registration fails

I’m not sure what this means. When you call SimpleInjector.Container.Verify(), Simple Injector will iterate through the registrations and resolve every one of them, and it will do so within the context of a SimpleInjector.Scope that is created and disposed for that occasion.

But if everything else fails, you can change the registration of ConsumeContext to take verification into consideration:

container.Register(
    () => container.GetInstance<ScopedConsumeContextProvider>().GetContext() ?? (
        container.IsVerifying
            ? new ConsumeContext()
            : throw new Exception("Some error")),
    Lifestyle.Scoped);

This returns a dummy ConsumeContext in case the container is currently verifying the graph. Do note, however, that other DI Containers might not consist of such a IsVerifying property (MS.DI certainly doesn’t).