My project structure is same as :
https://github.com/MarlabsInc/webapi-angularjs-spa
I have followed the instructions in :
http://docs.hangfire.io/en/latest/background-methods/using-ioc-containers.html
So I have created a container job activator.
In my Bootstrapper.cs
containerBuilder.RegisterType<DatabaseFactory>().As<IDatabaseFactory>().AsImplementedInterfaces().InstancePerApiRequest();
containerBuilder.RegisterType<UnitOfWork>().As<IUnitOfWork>().AsImplementedInterfaces().InstancePerApiRequest();
containerBuilder.RegisterApiControllers(System.Reflection.Assembly.GetExecutingAssembly());
IContainer container = containerBuilder.Build();
Hangfire.GlobalConfiguration.Configuration
.UseAutofacActivator(container);
JobActivator.Current = new AutofacJobActivator(container);
System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
My startup class has a method :
GlobalConfiguration.Configuration
.UseSqlServerStorage("entitiesDB",
new SqlServerStorageOptions
{
PrepareSchemaIfNecessary = false,
InvisibilityTimeout = TimeSpan.FromMinutes(30)
});
app.UseHangfireDashboard();
app.UseHangfireServer();
In controller : I am trying to update the status of 2000 invoices as "Approved"
So the method is straightforward as follows :
foreach(int id in invoiceIds)
{
BackgroundJob.Enqueue<IInvoiceService>(a => a.UpdateInvoice(id));
}
Now when I query in the SQL :
select * from HangFire.[State]
I get the following exception in Data column:
{"FailedAt":"2015-07-07T10:00:40.9454943Z","ExceptionType":"Autofac.Core.DependencyResolutionException","ExceptionMessage":"No
scope with a Tag matching 'AutofacWebRequest' is visible from the
scope in which the instance was requested. This generally indicates
that a component registered as per-HTTP request is being requested 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.","ExceptionDetails":"Autofac.Core.DependencyResolutionException:
No scope with a Tag matching 'AutofacWebRequest' is visible from the
scope in which the instance was requested. This generally indicates
that a component registered as per-HTTP request is being requested 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.\r\n at
Autofac.Core.Lifetime.MatchingScopeLifetime.FindScope(ISharingLifetimeScope
mostNestedVisibleScope)\r\n at
Autofac.Core.Resolving.InstanceLookup..ctor(IComponentRegistration
registration, IResolveOperation context, ISharingLifetimeScope
mostNestedVisibleScope, IEnumerable1 parameters)\r\n at
Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope
currentOperationScope, IComponentRegistration registration,
IEnumerable1 parameters)\r\n at
Autofac.Core.Resolving.ResolveOperation.ResolveComponent(IComponentRegistration
registration, IEnumerable1 parameters)\r\n at
Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration
registration, IEnumerable1 parameters)\r\n at
Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(IComponentRegistration
registration, IEnumerable1 parameters)\r\n at
Autofac.Core.Container.ResolveComponent(IComponentRegistration
registration, IEnumerable1 parameters)\r\n at
Autofac.ResolutionExtensions.TryResolveService(IComponentContext
context, Service service, IEnumerable1 parameters, Object&
instance)\r\n at
Autofac.ResolutionExtensions.ResolveService(IComponentContext context,
Service service, IEnumerable1 parameters)\r\n at
Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type
serviceType, IEnumerable`1 parameters)\r\n at
Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type
serviceType)\r\n at Hangfire.AutofacJobActivator.ActivateJob(Type
jobType)\r\n at Hangfire.Common.Job.Activate(JobActivator
activator)"}
Can someone please help me understand what I am doing wrong ?
You are using InstancePerApiRequest so your IoC container knows to create a new instance for each API request.
But, your background job is not executing inside an API request, so your IoC container does not know how to resolve your dependencies.
From the hangfire docs:
HttpContext is not available
Request information is not available during the instantiation of a target type. If you register your dependencies in a request scope (InstancePerHttpRequest in Autofac, InRequestScope in Ninject and so on), an exception will be thrown during the job activation process.
So, the entire dependency graph should be available. Either register additional services without using the request scope, or use separate instance of container if your IoC container does not support dependency registrations for multiple scopes.
According to this related SO question, Autofac does not support registering for multiple scopes out of the box so you will need to either:
Use a "lower" scope such as "InstancePerDependency", or
Use a separate IoC container for background jobs.
UPDATE:
The Hangfire Autofac integration package introduces a InstancePerBackgroundJob() extension method, and suggests that Autofac does support registering for multiple scopes like this:
builder.RegisterType<Database>()
.InstancePerBackgroundJob()
.InstancePerHttpRequest();
However, it's only available in the 1.2.0-beta1 release of Hangfire.Autofac which requires Hangfire 1.5.0-beta1.
Related
This question already has an answer here:
Override service implementation inside DI scope
(1 answer)
Closed 3 months ago.
I am trying to find a way to swap a certain service inside a ServiceProvider scope.
The actual problem that I am trying to solve is that we have a legacy app where current user is registered as a service, and returned from the current HttpContext, similar to:
services.AddScoped<ICurrentUserInfo>(sp =>
{
// simplified version, actually there is some HttpContext.Session caching,
// but the idea is that we're using HttpContext to get the current user
var context = sp.GetRequiredService<IHttpContextAccessor>();
return new CurrentUserInfo(context.HttpContext.User.Identity.Name);
});
Now the issue with this is that if I want to run some background tasks after startup init, all the services that have ICurrentUserInfo will fail because there is no HttpContext.Current. So I was hoping that I could do something like:
using (var scope = serviceProvider.CreateScope())
{
scope.ServiceProvider.Register<ICurrentUserInfo>(sp => /* get the "service" account */);
// Resolving the services inside this scope will now work,
// other services can still run and will use the original ICurrentUserInfo service
}
However I believe once the ServiceProvider is built from the service collection, it cannot be updated anymore.
Is there another way how this issue could be solved?
(Update) Found an existing answer here so closing this as a dupe.
Summary of the proposed solution would be to have a proxy class registered as a scoped service, and then inside the new scope we first manually resolve the proxy and swap the implementation before doing actual work. This way the change is transparent to callers and confined to the new scope.
As explained in the document:
HttpContext isn't thread-safe. Reading or writing properties of the HttpContext outside of processing a request can result in a NullReferenceException.
you have to move your codes into the processing of httprequest(middleware/controller/filer....) instead of change the lifetime of the service
According to the official documentation here:
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters#authorization-filters
To implement a custom ActionFilter in ASP.NET Core I have three choices:
SeviceFilterAttribute
TypeFilterAttribute
IFilterFactory
But for all three it is stated that:
Shouldn't be used with a filter that depends on services with a lifetime other than singleton.
So how can I inject scoped services in my custom ActionFilter? I can easily get a scoped service from the current HttpContext like this:
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
ISubscriptionHelper subscriptionHelper =
actionContext.HttpContext.RequestServices
.GetRequiredService<ISubscriptionHelper>();
}
But then I am wondering if I am doing something wrong? What is the correct way to depend on scoped services in a custom ActionFilterAttribute?
Resolving services from the HttpContext.RequestServices will correctly resolve scoped and transient instances without causing any problems such as Captive Dependencies. In case resolved components implement IDisposable, they will be disposed of when the request ends. ASP.NET Core passes on the current HttpContext object to filter's OnActionExecuting method and that HttpContext gives access to the DI Container.
This is completely different from injecting those services into the constructor, because the action filter will be cached for the lifetime of the application. Any dependencies stored in private fields will, therefore, live as long as that filter. This leads to the so called Captive Dependency problem.
Code that accesses the DI Container (the HttpContext.RequestServices is your gateway into the DI Container) should be centralized in the infrastructure code of the startup path of the application—the so called Composition Root. Accessing your DI Container outside the Composition Root inevitably leads to the Service Locator anti-pattern—this should not be taken lightly.
To prevent this, it is advised to keep the amount of code inside the action filter as small as possible and implement the filter as a Humble Object. This means that preferably, the only line of code inside the filter is the following:
actionContext.HttpContext.RequestServices
.GetRequiredService<ISomeService>() // resolve service
.DoSomeOperation(); // delegate work to service
This means all (application) logic is moved to the ISomeService implementation, allowing the action filter to become a Humble Object.
As a follow-up to this question, I wanted to understand how my invoking of a Service manually can be improved. This became longer than I wanted, but I feel the background info is needed.
When doing a pub/sub (broadcast), the normal sequence and flow in the Messaging API isn't used, and I instead get a callback when a pub/sub message is received, using IRedisClient, IRedisSubscription:
_subscription.OnMessage = (channel, msg) =>
{
onMessageReceived(ParseJsonMsgToPoco(msg));
};
The Action onMessageReceived will then, in turn, invoke a normal .NET/C# Event, like so:
protected override void OnMessageReceived(MyRequest request)
{
OnMyEvent?.Invoke(this, new RequestEventArgs(request));
}
This works, I get my request and all that, however, I would like it to be streamlined into the other flow, the flow in the Messaging API, meaning, the request finds its way into a Service class implementation, and that all normal boilerplate and dependency injection takes place as it would have using Messaging API.
So, in my Event handler, I manually invoke the Service:
private void Instance_OnMyEvent(object sender, RequestEventArgs e)
{
using (var myRequestService = HostContext.ResolveService<MyRequestService>(new BasicRequest()))
{
myRequestService.Any(e.Request);
}
}
and the MyRequestService is indeed found and Any called, and dependency injection works for the Service.
Question 1:
Methods such as OnBeforeExecute, OnAfterExecute etc, are not called, unless I manually call them, like: myRequestService.OnBeforeExecute(e) etc. What parts of the pipeline is lost? Can it be reinstated in some easy way, so I don't have to call each of them, in order, manually?
Question 2:
I think I am messing up the DI system when I do this:
using (var myRequestService = HostContext.ResolveService<MyRequestService>(new BasicRequest()))
{
myRequestService.OnBeforeExecute(e.Request);
myRequestService.Any(e.Request);
myRequestService.OnAfterExecute(e.Request);
}
The effect I see is that the injected dependencies that I have registered with container.AddScoped, isn't scoped, but seems static. I see this because I have a Guid inside the injected class, and that Guid is always the same in this case, when it should be different for each request.
container.AddScoped<IRedisCache, RedisCache>();
and the OnBeforeExecute (in a descendant to Service) is like:
public override void OnBeforeExecute(object requestDto)
{
base.OnBeforeExecute(requestDto);
IRedisCache cache = TryResolve<IRedisCache>();
cache?.SetGuid(Guid.NewGuid());
}
So, the IRedisCache Guid should be different each time, but it isn't. This however works fine when I use the Messaging API "from start to finish". It seems that if I call the TryResolve in the AppHostBase descendant, the AddScoped is ignored, and an instance is placed in the container, and then never removed.
What parts of the pipeline is lost?
None of the request pipeline is executed:
myRequestService.Any(e.Request);
Is physically only invoking the Any C# method of your MyRequestService class, it doesn't (nor cannot) do anything else.
The recommended way for invoking other Services during a Service Request is to use the Service Gateway.
But if you want to invoke a Service outside of a HTTP Request you can use the RPC Gateway for executing non-trusted services as it invokes the full Request Pipeline & converts HTTP Error responses into Typed Error Responses:
HostContext.AppHost.RpcGateway.ExecuteAsync()
For executing internal/trusted Services outside of a Service Request you can use HostContext.AppHost.ExecuteMessage as used by ServiceStack MQ which applies Message Request Request/Response Filters, Service Action Filters & Events.
I have registered with container.AddScoped
Do not use Request Scoped dependencies outside of a HTTP Request, use Singleton if the dependencies are ThreadSafe, otherwise register them as Transient. If you need to pass per-request storage pass them in IRequest.Items.
I want to acces dbcontext DI object after request finished, e.g. do some background task atfet request complete and keep it short.
sample code:
await bunusInfoService.CheckBonus(user.Id, _appSettings.GregSysRegisterIP);
UserDto uss = Mapper.Map<UserDto>(user);
return uss;
but call of bunusInfoService.CheckBonus without await.
it says:
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
I am registering my component like this:
public static void Register(IWindsorContainer container)
{
container.Register(Classes.FromAssembly(Assembly.GetAssembly(typeof(GenericBaseRepository)))
.InSameNamespaceAs<GenericBaseRepository>()
.WithService.DefaultInterfaces()
.LifestyleTransient());
}
I am then resolving it in a piece of code that has no HttpContext:
var baseRepository = ContainerManager.Container.Resolve<IBaseRepository>();
(IBaseRepository being an interface implemented by GenericBaseRepository). This fails with the following message:
"HttpContext.Current is null. PerWebRequestLifestyle can only be used in ASP.Net"
Which confuses me, because the lifestyle I choose is Transient, not PerWebRequest.
Of course, HttpContext doesn't exist during a scheduled task - but I don't really need it, I just want an instance of my Repository which will not interact with any web request.
So, why does Castle Windsor insist in requiring an HttpContext when resolving my component?
Have a look at the full exception message. Your root component may be transient but the exception indicates one of its dependencies uses per web request lifestyle.
Have a look at Windsor's diagnostics debugger view, that may help you pinpoint it.