cosullivan/SmtpServer

Scoped DI using WorkerService

stevie1706 opened this issue · 3 comments

Hi,

First of all thanks for the great library. :)

I am trying to setup the SMTP server inside of a background hosted worker process as per your example.

I have found that the instance of the messagestore is not scoped to the request and therefore registering services as scoped in the serviceprovider are keeping hold of state between receipt of messages.

Is this still a valid way of adding the SMTP server:

services.AddSingleton(
                provider =>
                {
                    var options = new SmtpServerOptionsBuilder()
                                        .Endpoint(builder =>
                                            builder
                                                .Port(588, false)
                                                .AllowUnsecureAuthentication(true)
                                        )
                                        .Certificate(serverCertificate)
                                        .SupportedSslProtocols(SslProtocols.Tls12 | SslProtocols.Tls13)
                                        .Build();

                    return new SmtpServer.SmtpServer(options, services.BuildServiceProvider());
                });

            services.AddHostedService<Worker>();

And if so, am I able to safely inject services into the message store, for example a dbcontext:

public class MyMessageStore : MessageStore
    {
        private readonly DbContext _myContext;

        public MyMessageStore(DbContext myContext)
        {
            _myContext = myContext;
        }

        public override async Task<SmtpResponse> SaveAsync(ISessionContext context, IMessageTransaction transaction, ReadOnlySequence<byte> buffer, CancellationToken cancellationToken)
        {
            _myContext.Add(//something);
            _myContext.SaveChanges();
        }
    }

Many thanks in advance.

Hi @stevie1706 ,

How are you registering your MyMessageStore? Are you registered using AddScoped?

Your issue here looks like it might be the way in which you are registering the components. You are registering the SmtpServer as a Singleton (which is fine because there will only be one SmtpServer instance running), however you are passing the IServiceProvider that is created from the call to services.BuildServiceProvider(). Whilst the BuildServiceProvider() will create a new scope, you are effectively passing that single scoped instance through to the SmtpServer as BuildServiceProvider will only ever be called when the SmtpServer is created.

Do you really need these services to be scoped? There are ways you can achieve this, but scoping might become more of a pain to implement if it doesn't really provide any benefit.

However, you could listen to the SessionCreated event and create a new service scope in there and assign that IServiceProvider instance to the e.Context (which is a property map that gets passed around for that session scope). Then instead of using IMessageStore you would use IMessageStoreFactory which you implementation could then pull out the IServiceProvider that is stored in the e.Context and create a scope instance.

Thanks, Cain.

Thanks Cain, unfortunately they do need to be scoped as our service layer is expecting a scoped database context. However, I will try out your SessionCreated method and see if that works for me. Appreciate the quick reply.

Many Thanks,
Steve

Just letting you know what you suggested worked fine. Thanks again.