Giter Club home page Giter Club logo

Comments (8)

dotnetjunkie avatar dotnetjunkie commented on September 27, 2024

What you are witnessing is an error in the documentation. You are correct that there is no RegisterExecutionContextScope extension method. This method does not exist, and there are no plans making it available in v3. Instead, you will have to create a ExecutionContextScopeLifestyle instance and pass it on the Register methods. In case you are using Web API, you can use RegisterWebApiRequest, which is in fact exactly the same, because this WebApiRequestLifestyle is in fact an ExecutionContextScopeLifestyle with a more convenient name.

To make working with scoped lifestyles more convenient, In v3 you will be able to do this:

var container = new Container();
container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();

container.Register<IUnitOfWork, DbUoW>(Lifestyle.Scoped);
container.Register<IUserContext, AspNetUserContext>(Lifestyle.Scoped);

from documentation.

mrchief avatar mrchief commented on September 27, 2024

Thanks! +1 for the Options. That thought did cross my mind when I was messing with scopes and was made to change all Register... lines to take the scope object.

On a side note, what led me down this path is the The XYZCommandHandler is registered as 'Hybrid Lifetime Scope / Web Request' lifestyle, but the instance is requested outside the context of a Hybrid Lifetime Scope / Web Request. issue but I think I'll post a separate thread for that, if I cannot figure out why its happening,

I'm using the Hybrid style since I have some ops that are executed in background using (HostingEnvironment.QueueBackgroundWorkItem) and I have a strong feeling that the ctor injected service is injected with a WebRequest scope but when the background op tries to access it, the scope is no longer valid. I feel that I need to exclusively control scope using BeginExecutionContextScope, something I thought I could avoid. I've gone thru your SO answers and was wondering if there is any other way other than creating a decorator...

from documentation.

dotnetjunkie avatar dotnetjunkie commented on September 27, 2024

The documentation contains an interesting section on working in multi-threaded applications. To reduce complexity. you should prevent moving object graphs from thread to thread. You should only use objects you resolve on the thread you resolved them on (or in the same asynchronous context in case you're using async). This keeps the knowledge about what is thread-safe and what is not inside your composition root and this reduces complexity.

So instead of passing your handler to the QueueBackgroundWorkItem, pass the command message and resolve a new graph inside the background thread. This will also be the place to start a new scope.

from documentation.

mrchief avatar mrchief commented on September 27, 2024

Talk about the joys of retrofitting old code! Let me show some code:

public class AbcService(IMediator mediator, IMailService mailService, IPushNotificationService  pushNotificationService, ...) : IAbcService 
{

   public async Task DoSomthingComplex(...)
   {
     ...
     var someValue = await _mediator.RequestAsync(command);

     HostingEnvironment.QueueBackgroundWorkItem(ct => mailService.SendEmail(...));
     HostingEnvironment.QueueBackgroundWorkItem(ct => pushNotificationService SendPushNotification(...));

     return someValue;
   }
}

IAbcService is injected in controller and so it gets created with WebRequest scope. Along with it, all dependent services as well.

By the time mailService and pushNotification service go around finishing their business, the web request scope is finished and hence the error message.

Resolving a new object graph makes sense. Let me try that!

from documentation.

dotnetjunkie avatar dotnetjunkie commented on September 27, 2024

What you are actually missing here is a proxy class for your IMailService. For instance:

public class BackgroundProcessingMailServiceProxy : IMailService
{
    private readonly Func<IMailService> serviceProvider;
    public BackgroundProcessingMailServiceProxy(Func<IMailService> serviceProvider) {
        this.serviceProvider = serviceProvider;
    }

    public void SendEmail(...) {
        HostingEnvironment.QueueBackgroundWorkItem(ct => {
            var mailService = serviceProvider.Invoke();
            mailService.SendEmail(...);
        });
    }
}

This class acts like a IMailService, but makes sure the mail is sent in the background. This class has the following advantages:

  • Consumers can now simply depend on IMailService without having to be concerned about doing any multi-threaded or background operations. This simplifies those classes considerably, and makes them testable again.
  • Logic related to multi-threading is moved to one place in the application: the composition root. This makes it much easier to reason about thread-safety.

Such abstraction can be registered as follows:

container.Register<IMailService, RealSmtpMailService>();
container.RegisterDecorator(typeof(IMailService), typeof(BackgroundProcessingMailServiceProxy),
    Lifestyle.Singleton);

Since the BackgroundProcessingMailServiceProxy both implements IMailService and wraps IMailService (or Func<IMailService> in this case), it can be considered a decorator and that's why Simple Injector allows you to use the RegisterDecorator method. But as easily you can make the following registration as well:

container.Register<RealSmtpMailService>();
container.RegisterSingle<IMailService>(
    new BackgroundProcessingMailServiceProxy(container.GetInstance<RealSmtpMailService>));

Does this help?

from documentation.

mrchief avatar mrchief commented on September 27, 2024

Very much. I did pretty much the same changes, except moving QBWI to the proxy but like you reasoned, moving it inside makes more sense.

Curious, what are the differences (if any) between the 2 registration styles?

I can see that the former is more semantic and readable and perhaps will not lock the container (as opposed to later which makes a GetInnstance call which will lock it), but other than that, is there any other difference?

from documentation.

dotnetjunkie avatar dotnetjunkie commented on September 27, 2024

When using RegisterDecorator, you will get full container support, including auto-wiring and diagnostics. This will especially be useful when the decorator itself contains dependencies. In that case, it is usually more convenient to let the container wire that type for you, especially because it allows you to check for common configuration mistakes, such as lifestyle mismatches.

The second registration on the other hand, would be useful in cases when the decorator has no other dependencies, or are dependencies that are not likely to change and are singletons as well. You lose the container's ability to diagnose this object graph, but if everything is a singleton already, verification might not be that interesting at all. Do note however that while the call to new BackgroundProcessingMailServiceProxy(container.GetInstance<RealSmtpMailService>) does create an BackgroundProcessingMailServiceProxy instance, it does not request an RealSmtpMailService from the container, but what happens here is that the C# compiler wraps this method in a Func<T> delegate, which is injected into the proxy's constructor. Since the Func<T> is only called when the proxy's SendMail(...) method is called, so will the GetInstance<RealSmtpMailService>() method call. This means that at this point where the proxy is created, the container isn't locked at all.

One reason to use the second method over the register decorator, is because you might not see this really as decoration, in which case using RegisterDecorator might confuse other developers.

from documentation.

mrchief avatar mrchief commented on September 27, 2024

Works like a charm! I also realized that with this approach, I don't need a scoped or hybrid lifestyle. In fact, using a hybrid lifestyle throws the same ...instance is requested outside the context of a Hybrid Lifetime Scope / Web Request... error for DbContext object. Not sure if that's supposed to happen though. The DbContext is registered as container.Register<DbContext, DbContext>();

Thanks for the help! 👍

from documentation.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.