castleproject / Windsor

Castle Windsor is a best of breed, mature Inversion of Control container available for .NET
http://www.castleproject.org
Apache License 2.0
1.52k stars 456 forks source link

Castle.Windsor.Extensions.DependencyInjection No Scope Available #595

Open EricMKaufman opened 3 years ago

EricMKaufman commented 3 years ago

I am using this nuget package to configure Castle Windsor as the DI container. https://www.nuget.org/packages/Castle.Windsor.Extensions.DependencyInjection/

This works fine except for when I try to resolve scoped classes with an IHttpHandler. I get the error: InvalidOperationException: No scope available

What is the correct way to create scope for an HttpHandler?

Here's a sample application reproducing the issue:


namespace WebApplication1
{
    public class Global : System.Web.HttpApplication
    {
        public static IWindsorContainer Container;

        protected void Application_Start(object sender, EventArgs e)
        {
            Console.WriteLine("Hello World!");

            Container = new WindsorContainer();
            var host = new HostBuilder()
                .UseWindsorContainerServiceProvider(Container)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddScoped<SomeService>();
                })
                .Build();

            //Note: This works great
            var resolved = Container.Resolve<SomeService>();
        }

        protected void Session_Start(object sender, EventArgs e)
        {

        }

        protected void Application_BeginRequest(object sender, EventArgs e)
        {

        }

        protected void Application_AuthenticateRequest(object sender, EventArgs e)
        {

        }

        protected void Application_Error(object sender, EventArgs e)
        {

        }

        protected void Session_End(object sender, EventArgs e)
        {

        }

        protected void Application_End(object sender, EventArgs e)
        {

        }
    }

    public class SomeService
    {

    }
    public class HttpHandler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            using (var scope = Global.Container.BeginScope())
            {
                //this errors
                var someService = Global.Container.Resolve<SomeService>();
            }
        }

        public bool IsReusable => false;
    }

}

Web.config:

<system.webServer>
        <handlers>
            <add verb="*" path="*" type="WebApplication1.HttpHandler, WebApplication1, Version=1.0.0.0, Culture=neutral" name="default" />
        </handlers>
    </system.webServer>
Z10yTap0k commented 3 years ago

I have related issue:

I use IHostedService. all services registered with ConfigureContainer extension of IHostBuilder. SimpleSingletone registered as Singleton lifestyle. Service IAnyScopedService as Scoped

public class SimpleSingletone
{
       private readonly IServiceScopeFactory  _serviceScopeFactory;
       public SimpleSingletone(IServiceScopeFactory serviceScopeFactory)
       {
           _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(IServiceScopeFactory ))
       }
       public string Get()
       {
          var scope = _serviceScopeFactory.CreateScope();
          try
          {
              var scopedService = scope.ServiceProvider.GetService<IAnyScopedService>();
              return scopedService.Get(userId);
          }
          finally
          {
              scope.Dispose();
          }
       }
}

scope.ServiceProvider.GetService<> was throw No Scope.

if i replace IServiceScopeFactory IWindsorContainer all works perfectly.

EricMKaufman commented 3 years ago

I was able to come up with a work around, but I think it's a bit of a hack and I'm unsure how it will effect releasing scoped resources.

The below creates a class that derives from WindsorServiceProviderFactoryBase so it can create a root scope on the executing thread.

namespace WebApplication1
{
    public class Global : System.Web.HttpApplication
    {
        public static WindsorServiceProviderFactoryCustom ServiceProviderFactory;

        protected void Application_Start(object sender, EventArgs e)
        {
            Console.WriteLine("Hello World!");

            var container = new WindsorContainer();

            ServiceProviderFactory = new WindsorServiceProviderFactoryCustom(container);

            var host = new HostBuilder()
                .UseWindsorContainerServiceProvider(ServiceProviderFactory)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddScoped<SomeService>();
                })
                .Build();

            //Note: This works great
            var resolved = ServiceProviderFactory.Container.Resolve<SomeService>();
        }

        protected void Session_Start(object sender, EventArgs e)
        {

        }

        protected void Application_BeginRequest(object sender, EventArgs e)
        {

        }

        protected void Application_AuthenticateRequest(object sender, EventArgs e)
        {

        }

        protected void Application_Error(object sender, EventArgs e)
        {

        }

        protected void Session_End(object sender, EventArgs e)
        {

        }

        protected void Application_End(object sender, EventArgs e)
        {

        }
    }

    public class SomeService
    {
    }

    public class HttpHandler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            //This fixes the error, but I am too unfamiliar with the internals of this library to understand if this is a reasonable workaround. Will the component be scoped to `scope` below?
            Global.ServiceProviderFactory.CreateRootScope();

            using (var scope = Global.ServiceProviderFactory.Container.BeginScope())
            {
                //this no longer errors, but at what cost.
                var someService = Global.ServiceProviderFactory.Container.Resolve<SomeService>();
            }
        }

        public bool IsReusable => false;
    }

    public sealed class WindsorServiceProviderFactoryCustom : WindsorServiceProviderFactoryBase
    {
        public WindsorServiceProviderFactoryCustom(IWindsorContainer container)
        {
            CreateRootScope();
            SetRootContainer(container);
        }

        public new void CreateRootScope()
        {
            base.CreateRootScope();
        }
    }

}
ltines commented 3 years ago

I think this has been fixed as CreateRootScope is called in WindsorServiceProviderFactory constructor.

I think the cleanest way to do it though is:

lmorvan commented 9 months ago

I have this issue with the latest versions of Castle.Windsor 6.0.0 and Castle.Windsor.Extensions.Hosting 6.0.0. I call this code on my ASP.NET 6.0 server:

var builder = WebApplication.CreateBuilder();
var container = new WindsorContainer();
builder.Host.UseWindsorContainerServiceProvider(container);

And when running my server (with gRPC services, and a Yarp reverse proxy configured), I get this error:

An unhandled exception has occurred while executing the request.
      System.InvalidOperationException: No scope available
         at Castle.Windsor.Extensions.DependencyInjection.Scope.ExtensionContainerScopeCache.get_Current()
         at Castle.Windsor.Extensions.DependencyInjection.Scope.ForcedScope..ctor(ExtensionContainerScopeBase scope)
         at Castle.Windsor.Extensions.DependencyInjection.WindsorScopedServiceProvider.GetRequiredService(Type serviceType)
         at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
         at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
         at Microsoft.AspNetCore.Routing.Matching.DfaMatcherFactory.CreateMatcher(EndpointDataSource dataSource)
         at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.InitializeCoreAsync()
      --- End of stack trace from previous location ---

I have checked the method CreateRootScope is indeed called in the underlying WindsorServiceProviderFactory.

Any idea? 😄

Addendum: The exact same code used to work with the previous version / package Caste.Windsor.Extensions.DependencyInjection 5.1.1.