I have .NET Core web API project, for some reasons, we created a background service in this project and start running the background service while the application is started.
So, we created a BackgroundWorkderService, which inherited from BackgroundService (Microsoft.Extensions.Hosting) like below:
public class BackgroundWorkerService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await DoWork(stoppingToken);
}
public override async Task StartAsync(CancellationToken cancellationToken)
{
await ExecuteAsync(cancellationToken);
}
public override Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
To run it while the application starts, I added the background service to the hosted service in Program.cs as below:
.ConfigureServices(services =>
services.AddHostedService<BackgroundWorkerService>());
Now, we need to create an integration test, and we want to stop the background service while we are running the integration test.
Does anyone know how to stop it in the integration test? I have tried to remove the service from ConfigureTestServices, but no luck with it, the background service still runs when the integration test starts.
You should be able to, as you mentioned in the question, remove the service in ConfigureTestServices. However, exactly how we specify which service to remove is the key here.
In my experience, you need to find the ServiceDescriptor in question rather than create a new one with the expected values (see note below for why). I use LINQ and the implementation type of the service (in your case, BackgroundWorkerService) to easily find the existing ServiceDescriptor. Then I can remove it from the list, which means it won't be started when CreateClient() is called on the WebApplicationFactory.
Looks something like this:
builder.ConfigureTestServices(services =>
{
var descriptor = services.Single(s => s.ImplementationType == typeof(BackgroundWorkerService));
services.Remove(descriptor);
}
A really nice benefit of this approach is that it keeps test logic out of the production code and entirely in the setup/fixture for your tests.
Note on IServiceCollection removals: With a bit of poking, I believe this is because ServiceDescriptor doesn't provide an equality or comparison method other than Object.Equals, which falls back to reference equality. So even if all the values are the same in a new ServiceDescriptor, they would different objects and thus won't be found and removed from the service list.
I found a solution to put the background service register in a condition as below.
Edit the Program.cs file as below in the section of registering your background service:
.ConfigureServices(services =>
{
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "INTEGRATION")
{
services.AddHostedService<BackgroundWorkerService>();
}
});
Then try to change the variable to be INTEGRATION from where you need.
Other answers are correct, but I wanted to add a little supplement as I found why RemoveAll could still be used.
According to the dotnet source code the RemoveAll method uses ServiceType and not ImplementationType to find the service.
/// <summary>
/// Removes all services of type <paramref name="serviceType"/> in <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="collection">The <see cref="IServiceCollection"/>.</param>
/// <param name="serviceType">The service type to remove.</param>
/// <returns>The <see cref="IServiceCollection"/> for chaining.</returns>
public static IServiceCollection RemoveAll(this IServiceCollection collection, Type serviceType)
{
ThrowHelper.ThrowIfNull(serviceType);
for (int i = collection.Count - 1; i >= 0; i--)
{
ServiceDescriptor? descriptor = collection[i];
if (descriptor.ServiceType == serviceType)
{
collection.RemoveAt(i);
}
}
return collection;
}
This means that we can't use actual service type of BackgroundWorkerService (as in the question) but instead the type it was registered with which is IHostedService
This means that this code can be used to nuke all background workers from existence.
services.RemoveAll<IHostedService>();
Unfortunately this removes also some workers that are registered by the asp.net framework, and they need to be reintroduced.
I faced same issue so I've extended WebApplicationFactory class and removed hosted service before by overriding CreateHost
public class CustomWebApplicationFactory<T> : WebApplicationFactory<T>
where T : class
{
protected override IHost CreateHost(IHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var descriptor = services.Single(s => s.ImplementationType == typeof(BackgroundService));
services.Remove(descriptor);
});
return base.CreateHost(builder);
}
}
Related
I have an extension to enqueue my view models pointing to an implementation of an interface IBackgroundJob
this are my extensions methods
private static readonly ActivitySource activitySource = new("MC.Hangfire.Extensions");
public static string Enqueue<T>(this T job, IBackgroundJobClient client)
{
return client.Enqueue<IBackgroundJob<T>>(ps => ps.AddTelemetry(null).EnqueueJob(null, job, JobCancellationToken.Null));
}
public static IBackgroundJob<T> AddTelemetry<T>(this IBackgroundJob<T> job, PerformContext context)
{
using var activity = activitySource.StartActivity($"Start Job {typeof(T).FullName} id {context.BackgroundJob.Id}", ActivityKind.Server);
activity?.SetTag("JobId", context.BackgroundJob.Id);
activity?.SetTag("JobJson", Newtonsoft.Json.JsonConvert.SerializeObject(job));
activity?.SetTag("Job", Newtonsoft.Json.JsonConvert.SerializeObject(context.BackgroundJob.Job));
return job;
}
My problem is that the EnqueueJob method is called, but the AddTelemetry method is not called before, how can I Add the telemetry information before calling all of my jobs, but in the context of the jobs, and of course not adding this code in all of my enqueue methods?
I'm looking for the Hangfire filters, but I think that there should be a way to inject the filter with the DI of the ASP.NET core application
I created this issue on github because I think that the problem with instrumentation is a little deeper in the code
https://github.com/HangfireIO/Hangfire/issues/2017
I have the Quartz Job shown below
public class ExtractTradesJob: IJob
{
private ITradeExtractor _tradeExtractor;
public ExtractTradesJob(ITradeExtractor tradeExtractor)
{
_tradeExtractor = tradeExtractor;
}
public async Task GetTradesAsync(DateTime dateTime)
{
Console.WriteLine(dateTime);
}
void IJob.Execute(IJobExecutionContext context)
{
Task.Run(async () => await GetTradesAsync(DateTime.Now));
}
}
I have 2 issues
As I need to use .NET 4.5, Async support is not available out of the box so is the method I have used for calling my async method correct?
It appears as though if I have a constructor as in my case, the job does not fire. I have checked and I know the the ITradeExtractor service is registered correctly. So how can I have a job that takes a service in its constructor? If I remove the constructor, my Execute method is called correctly
I am using AutoFac 3.5.2 and Quartz 2.6.2 and AutoFac.Extras.Quartz 3.5.0
I am using the code below to setup AutoFac
public static void RegisterWithAutofac(ContainerBuilder builder)
{
builder.RegisterType<TradeExtractor>()
.As<ITradeExtractor>()
.SingleInstance();
builder.Register(x => new StdSchedulerFactory().GetScheduler()).As<IScheduler>();
}
I know these are old packages, but my limitation of having to use .NET 4.5 means this is out of my control
Paul
Trying to use:
Startup.cs
public void ConfigureServices(IServiceCollection services) {
services.AddHostedService<LifetimeEvents>();
.
.
.
}
where the LifeTimeEvents class inherits from IHostedService. I get this error:
'IServiceCollection' does not contain a definition for 'AddHostedService' and no extension method 'AddHostedService' accepting a first argument of type 'IServiceCollection' could be found (are you missing a using directive or an assembly reference?)
I can't seem to find the proper namespace to use or nuget package to include to get this working, but it worked out of the box in .NET Core 2.1, is this just not available in .NET Core 2.0? Is there any way to get it working?
UPDATE:
As a workaround I changed my code to use:
Startup.cs
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton<LifetimeEvents>();
.
.
.
}
public void Configure(IApplicationBuilder appBuilder, IHostingEnvironment envHost, LifetimeEvents appEvents) {
appEvents.StartAsync(new CancellationToken(false));
.
.
.
}
and that seems to have done the job. Doesn't answer my original question, and I'm not sure how "best practices" it is, but it did get me moving refactoring this .NET Core 2.0 app.
is this just not available in .NET Core 2.0?
ServiceCollectionHostedServiceExtensions.AddHostedService(IServiceCollection) Method as shown in the API reference
Applies to
ASP.NET Core
2.1
However the source code is available on GitHub. You can easily check it out there and copy a local version to your 2.0 project
namespace Microsoft.Extensions.DependencyInjection
{
public static class ServiceCollectionHostedServiceExtensions
{
/// <summary>
/// Add an <see cref="IHostedService"/> registration for the given type.
/// </summary>
/// <typeparam name="THostedService">An <see cref="IHostedService"/> to register.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to register with.</param>
/// <returns>The original <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddHostedService<THostedService>(this IServiceCollection services)
where THostedService : class, IHostedService
=> services.AddTransient<IHostedService, THostedService>();
}
}
Source code
Ideally you could just update the project to 2.1 where the extension becomes available.
I believe this is a duplicate question to what I've answered before.
Where am I supposed to start persistent background tasks in ASP.NET Core?
Below is the answer, copy + pasted.
I believe you're looking for this
https://blogs.msdn.microsoft.com/cesardelatorre/2017/11/18/implementing-background-tasks-in-microservices-with-ihostedservice-and-the-backgroundservice-class-net-core-2-x/
And i did a 2 hour self-proclaimed-award-winning hackathon against myself to learn abit of that.
https://github.com/nixxholas/nautilus
You can refer the injections here and implement the abstracts from there too.
Many MVC projects are not really required to operate persistent background tasks. This is why you don't see them baked into a fresh new project via the template. It's better to provide developers an interface to tap on and go ahead with it.
Also, with regards to opening that socket connection for such background tasks, I have yet to establish a solution for that. As far as I know/did, I was only able to broadcast payload to clients that are connected to my own socketmanager so you'll have to look elsewhere for that. I'll definitely beep if there is anything regarding websockets in an IHostedService.
Ok anyway here's what happens.
Put this somewhere in your project, its more of an interface for you to overload with to create your own task
/// Copyright(c) .NET Foundation.Licensed under the Apache License, Version 2.0.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
protected readonly IServiceScopeFactory _scopeFactory;
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts =
new CancellationTokenSource();
public BackgroundService(IServiceScopeFactory scopeFactory) {
_scopeFactory = scopeFactory;
}
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it,
// this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
Here's how you can actually use it
public class IncomingEthTxService : BackgroundService
{
public IncomingEthTxService(IServiceScopeFactory scopeFactory) : base(scopeFactory)
{
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = _scopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<NautilusDbContext>();
Console.WriteLine("[IncomingEthTxService] Service is Running");
// Run something
await Task.Delay(5, stoppingToken);
}
}
}
}
If you noticed, there's a bonus there. You'll have to use a servicescope in order to access db operations because its a singleton.
Inject your service in
// Background Service Dependencies
services.AddSingleton<IHostedService, IncomingEthTxService>();
I have been reading the official Session and application state documentation and have stumbled upon the following paragraph:
Loading Session asynchronously
The default session provider in ASP.NET Core loads the session record
from the underlying IDistributedCache store asynchronously only if the
ISession.LoadAsync method is explicitly called before the TryGetValue,
Set, or Remove methods. If LoadAsync is not called first, the
underlying session record is loaded synchronously, which could
potentially impact the ability of the app to scale.
To have applications enforce this pattern, wrap the
DistributedSessionStore and DistributedSession implementations with
versions that throw an exception if the LoadAsync method is not called
before TryGetValue, Set, or Remove. Register the wrapped versions in
the services container.
The wrapping itself is not an issue for me, but in order to implement it, I need:
Reference to the original implementation
Registering the wrapped version
Currently, I have created the following wrapper class:
public class WrappedDistributedSession : ISession
{
private DistributedSession _service;
private bool loaded = false;
public WrappedDistributedSession(DistributedSession service)
{
_service = service;
}
public bool IsAvailable => _service.IsAvailable;
public string Id => _service.Id;
public IEnumerable<string> Keys => _service.Keys;
public void Clear() => _service.Clear();
public Task CommitAsync() => _service.CommitAsync();
public Task LoadAsync()
{
loaded = true;
return _service.LoadAsync();
}
public void Remove(string key)
{
if(loaded)
{
_service.Remove(key);
} else
{
throw new Exception();
}
}
public void Set(string key, byte[] value)
{
if (loaded)
{
_service.Set(key, value);
}
else
{
throw new Exception();
}
}
public bool TryGetValue(string key, out byte[] value)
{
if (loaded)
{
return _service.TryGetValue(key, out value);
}
else
{
throw new Exception();
}
}
}
And I have registered it in the Startup.ConfigureServices
services.AddScoped<ISession, WrappedDistributedSession>();
Obviously, since I am writing this question, my solution does not work. Where did I go wrong and how does one "Register the wrapped versions in the services container"?
Use at your risk. This seems to work in Configure method just after sessions.
This solution is an adaptation from this unit test:
https://github.com/dotnet/aspnetcore/blob/cd0eab88eaa230fa276c27ab5dc71ea267efe14f/src/Middleware/Session/test/SessionTests.cs#L654-L656
app.UseSession();
app.Use(async (context, next) =>
{
await context.Session.LoadAsync();
await next();
});
Or as a more qualified wrapper extension:
public static class SesssionAsyncExtensions
{
/// <summary>
/// Have sessions be asyncronous. This adaptation is needed to force the session provider to use async calls instead of syncronous ones for session.
/// Someone surprisingly for something that seems common, Microsoft didn't make this aspect super nice.
/// </summary>
/// <param name="app">App builder instance.</param>
/// <returns>App builder instance for chaining.</returns>
/// <remarks>
/// From Microsoft Documentation (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-5.0):
/// The default session provider in ASP.NET Core will only load the session record from the underlying IDistributedCache store asynchronously if the
/// ISession.LoadAsync method is explicitly called before calling the TryGetValue, Set or Remove methods.
/// Failure to call LoadAsync first will result in the underlying session record being loaded synchronously,
/// which could potentially impact the ability of an application to scale.
///
/// See also:
/// https://github.com/dotnet/aspnetcore/blob/d2a0cbc093e1e7bb3e38b55cd6043e4e2a0a2e9a/src/Middleware/Session/src/DistributedSession.cs#L268
/// https://github.com/dotnet/AspNetCore.Docs/issues/1840#issuecomment-454182594
/// https://bartwullems.blogspot.com/2019/12/aspnet-core-load-session-state.html
/// </remarks>
public static IApplicationBuilder UseAsyncSession(this IApplicationBuilder app)
{
app.UseSession();
app.Use(async (context, next) =>
{
await context.Session.LoadAsync();
await next();
});
return app;
}
}
It seems you need to implement ISessonStore too (which is actually mentioned in the documentation you quoted), as it's the only one registered in AddSession extension method.
public static IServiceCollection AddSession(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddTransient<ISessionStore, DistributedSessionStore>();
services.AddDataProtection();
return services;
}
ISessionStore (and hence DistributedSessionStore) has a Create (see source) method which returns ISession. Here you need to return your custom implementation.
https://github.com/aspnet/Session/blob/rel/1.1.0/src/Microsoft.AspNetCore.Session/SessionServiceCollectionExtensions.cs#L27-L29
Then you can add before AddSession with
services.AddTransient<ISessionStore, AsyncDistributedSessionStore>();
I have 7 services running under Service Fabric. I decided to create a generic OwinCommunicationsListener class since the code can be very generic.
I noticed that the template for Service Fabric sets up the startup class (which configures the pipeline) as a static class and the stateless service class passes it as an action
internal sealed class WebService : StatelessService
{
public WebService(StatelessServiceContext context)
: base(context)
{ }
/// <summary>
/// Optional override to create listeners (like tcp, http) for this service instance.
/// </summary>
/// <returns>The collection of listeners.</returns>
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new ServiceInstanceListener[]
{
new ServiceInstanceListener(serviceContext => new OwinCommunicationListener(Startup.ConfigureApp, serviceContext, ServiceEventSource.Current, "ServiceEndpoint"))
};
}
}
If I need to do DI, I need to pass those objects to the startup class. From the template, the only way I can see to do that is to either set those objects up in OwinCommunicationsListener or pass parameters to OwinCommunicationsListener - either of which will mean OwinCommunicationsListener is less generic.
I noticed in the WordCount example, they went with a normal startup class and passed a reference to it over to OwinCommunicationsListenter. This way the ServiceClass can pass some objects to Startup which it can use for DI and OwinCommunicationsListener can remain generic.
public class WordCountWebService : StatelessService
{
public WordCountWebService(StatelessServiceContext context)
: base(context)
{
}
/// <summary>
/// Creates a listener for Web API with websockets.
/// </summary>
/// <returns>The OWIN communication listener.</returns>
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new[]
{
new ServiceInstanceListener(initParams => new OwinCommunicationListener("wordcount", new Startup(MyDIObject), initParams))
};
}
}
If I think of the stateless service class as the brains and the OwinCommunicationsListener class as a generic helper shared by several services, it seems I should go the same route as the wordcount example and have a non-static startup class. Are there any downsides to this approach? I wondered why the templates would not use that approach when the idea of microservices is that we will have a lot of them and generic scaffolding like this can improve maintenance and reliability.
The wordcount example is what I would recommend. No downsides that I know of.
Have you looked at ASP.NET Core? It gets even easier with IWebHost with its built-in DI. Here is an example: https://github.com/vturecek/service-fabric-xray/blob/master/src/xray.Data/DataService.cs