Scope error when using Autofac with SignalR

2019-04-08 08:58发布

问题:

I'm trying to inject an HttpContextBase in my SignalR hub:

public class EventHub : Hub, IDisconnect
{
    private readonly HttpContextBase _httpContextBase;

    public EventHub(HttpContextBase httpContextBase)
    {
        _httpContextBase = httpContextBase;
    }

    [...]
}

The registration code looks like this:

private static void InitAutofac()
{
    var builder = new ContainerBuilder();

    var assembly = typeof (MvcApplication).Assembly;

    builder.RegisterControllers(assembly).PropertiesAutowired();
    builder.RegisterModule(new AutofacWebTypesModule());
    builder.RegisterFilterProvider();

    builder.RegisterAssemblyTypes(assembly)
        .Where(InterfaceBasedInjectedClasses())
        .AsImplementedInterfaces()
        .InstancePerLifetimeScope();

    builder.RegisterAssemblyTypes(assembly)
        .Where(InterfaceLessInjectedClasses())
        .InstancePerLifetimeScope();

    builder.RegisterType<SurvivalContainer>().InstancePerLifetimeScope();

    builder.RegisterType<EventHub>().InstancePerLifetimeScope();

    var container = builder.Build();

    DependencyResolver.SetResolver(new AutofacDependencyResolver(container));


    GlobalHost.DependencyResolver = new SignalR.Autofac.AutofacDependencyResolver(container);

    RouteTable.Routes.MapHubs();
}

And the error I'm getting is:

[DependencyResolutionException: No scope with a Tag matching 'httpRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being reqested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself.]

Stacktrace:

Autofac.Core.Lifetime.MatchingScopeLifetime.FindScope(ISharingLifetimeScope mostNestedVisibleScope) +160
Autofac.Core.Resolving.InstanceLookup..ctor(IComponentRegistration registration, IResolveOperation context, ISharingLifetimeScope mostNestedVisibleScope, IEnumerable`1 parameters) +57
Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable`1 parameters) +102
Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable`1 parameters) +64
Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters) +164
Autofac.Core.Container.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters) +14
Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance) +70
Autofac.ResolutionExtensions.TryResolve(IComponentContext context, Type serviceType, Object& instance) +70
SignalR.Autofac.AutofacDependencyResolver.GetService(Type serviceType) in D:\etc\Dev\SignalR.Autofac\AutofacDependencyResolver.cs:30
SignalR.Hubs.DefaultHubActivator.Create(HubDescriptor descriptor) +60
SignalR.Hubs.DefaultHubManager.ResolveHub(String hubName) +27
SignalR.Hubs.HubDispatcher.CreateHub(IRequest request, HubDescriptor descriptor, String connectionId, TrackingDictionary state, Boolean throwIfFailedToCreate) +445
SignalR.Hubs.HubDispatcher.OnReceivedAsync(IRequest request, String connectionId, String data) +246
SignalR.<>c__DisplayClass6.<ProcessRequestAsync>b__4(String data) +29
SignalR.Transports.ForeverTransport.ProcessSendRequest() +63
SignalR.Transports.ForeverTransport.ProcessRequestCore(ITransportConnection connection) +70
SignalR.Transports.ForeverTransport.ProcessRequest(ITransportConnection connection) +5
SignalR.PersistentConnection.ProcessRequestAsync(HostContext context) +560
SignalR.Hubs.HubDispatcher.ProcessRequestAsync(HostContext context) +120
SignalR.Hosting.AspNet.AspNetHandler.ProcessRequestAsync(HttpContextBase context) +422       SignalR.Hosting.AspNet.HttpTaskAsyncHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +68
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +301
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

I'm just getting started with autofac and SignalR and I find myself a little stuck. None of the related topics on SO helped. Any idea on what I'm doing wrong?

回答1:

The problem is this line:

builder.RegisterModule(new AutofacWebTypesModule());

The exception message indicates that, during resolution, something is trying to be resolved from a lifetime scope tagged httpRequest. You get registrations like this in Autofac when you register something InstancePerHttpRequest():

// These two are roughly equivalent:
builder.RegisterType<SomeType>().InstancePerHttpRequest();
builder.RegisterType<SomeType>().InstancePerMatchingLifetimeScope("AutofacWebRequest");

If you look at the source of the AutofacWebTypesModule it registers the web abstractions (like HttpContextBase, the thing you're looking for) as InstancePerHttpRequest.

Further, if you look at the way the Autofac.Integration.Mvc.AutofacDependencyResolver works, whenever you resolve a type during a request, it creates a new, nested, named lifetime scope tagged with httpRequest. This allows you to have that magic InstancePerHttpRequest.

Under the assumption the SignalR.Autofac library you're using is the one here, which is also the one on NuGet), looking in the SignalR.Autofac.AutofacDependencyResolver, no such nested/named lifetime scope is being created for service resolution.

Thus, when Autofac tries to resolve the HttpContextBase dependency, it can't find that httpRequest tagged scope (because it doesn't exist) and issues the error you're seeing.

There is no simple answer for this. The nested httpRequest scope is actually important because it basically can't exist outside of a real web request. It makes it "safe" - you can't get an HttpContextBase if there's no web context (say, at application startup).

If you need to inject an HttpContextBase and you're sure that your EventHub will only live for one web request and that's it (I'm no SignalR guy, so bear with me), it means you either need to:

  • Request a fix for this from the SignalR.Autofac project.
  • Implement your own custom SignalR.Autofac.AutofacDependencyResolver that handles things the way the MVC resolver does.

Without actually doing the work and testing it out myself, I can't really provide specific guidance on how to accomplish that. This exercise is left to the reader.