cosullivan / SmtpServer

A SMTP Server component written in C#
MIT License
676 stars 160 forks source link

Scoped DI using WorkerService #167

Closed stevie1706 closed 8 months ago

stevie1706 commented 3 years ago

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.

cosullivan commented 3 years ago

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.

stevie1706 commented 3 years ago

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

stevie1706 commented 3 years ago

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