Readiness Healthcheck with warmup C# - c#

I am using Readiness healthchecks for my project and want to add a warmup period to it.
Dependency Injection is being used to get the warmup task from the Kernel but I am not able to get it because the Readiness Healthcheck is being initialized before the IKernel it seems.
I am getting the follow error:
Unable to resolve service for type 'IKernel' while attempting to activate 'Project.Ranking.API.HealthCheck.RankingReadinessHealthCheck'.
How does one use a class to warm up the pod before it is being used.
I have not been able to find a working example where someone warms up before the endpoints are available.
UPDATE:
Core.Library Startup.CS
public void CoreConfigureServices(IServiceCollection services)
{
... other code
services.AddHealthChecks()
.AddIdentityServer("https://identity.example.com")
.AddCheck<IReadinessHealthCheck>("Readiness", failureStatus: null)
.AddCheck<ILivenessHealthCheck>("Liveness", failureStatus: null);
services.AddSingleton<ILivenessHealthCheck, LivenessHealthCheck>();
}
public void CoreConfigure(IApplicationBuilder app, IHostEnvironment env)
{
... other code
app.UseHealthChecks("/healthcheck/live", new HealthCheckOptions()
{
Predicate = check => check.Name == "Liveness"
});
app.UseHealthChecks("/healthcheck/ready", new HealthCheckOptions()
{
Predicate = check => check.Name == "Readiness",
});
}
API Startup.CS
public void ConfigureServices(IServiceCollection services)
{
CoreConfigureServices(services);
... other code
services.AddSingleton<Core.Library.IReadinessHealthCheck, ReadinessHealthCheck>();
}
public void Configure(IApplicationBuilder app, IHostEnvironment env)
{
CoreConfigure(app, env);
... other code
//Here used to be the warm up, but this is used in the liveness probe and i want to warm up in the readiness probe
//Kernel.Get<IWarmupTask>().Initialize();
Kernel.Bind<IReadinessHealthCheck>().To<ReadinessHealthCheck>();
}
Core.Library BaseReadinessHealthCheck.cs
public abstract class BaseReadinessHealthCheck : IReadinessHealthCheck
{
public BaseReadinessHealthCheck()
{
}
private bool StartupTaskCompleted { get; set; } = false;
public abstract void WarmUp();
public void CompleteTask()
{
StartupTaskCompleted = true;
}
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
//start tasks
if (!StartupTaskCompleted)
{
Task.Run(() => WarmUp());
}
if (StartupTaskCompleted)
{
return Task.FromResult(HealthCheckResult.Healthy("The startup task is finished."));
}
return Task.FromResult(HealthCheckResult.Unhealthy("The startup task is still running."));
}
}
API ReadinessHealthCheck.CS
public class ReadinessHealthCheck : ReadinessHealthCheck
{
public ReadinessHealthCheck(IKernel kernel) : base(kernel)
{
}
public override void WarmUp()
{
// I want to do a warmup here, where it calls IWarmupTask
CompleteTask();
}
}

Related

Hangfire recurring job to call method is Startup using existing services

I'm trying to setup Hangfire to run a recurring job within Startup.cs with a standalone method. For this to work, I need to grab some ApplicationServices I have injected already. But the job execution fails with this error:
Recurring job 'Job: Dispatch Email from Queue' can't be scheduled due to an error and will be retried in 00:00:15.
System.InvalidOperationException: Recurring job can't be scheduled, see inner exception for details.
---> Hangfire.Common.JobLoadException: Could not load the job. See inner exception for the details.
---> Newtonsoft.Json.JsonSerializationException: Could not create an instance of type DA.BrrMs.Application.Interfaces.ServiceInterfaces.IBudgetReleaseRequestService. Type is an interface or abstract class and cannot be instantiated.
Here is what I have:
public class Startup
{
private IConfiguration Configuration { get; }
private RecurringJobManager _jobManager;
public Startup(IConfiguration configuration) => Configuration = configuration;
public void ConfigureServices(IServiceCollection services)
{
...
if (Configuration["SystemSettings:UseHangfire"] == "1")
{
services.AddHangfire(c => c.UseMemoryStorage());
JobStorage.Current = new MemoryStorage();
services.AddHangfireServer();
GlobalConfiguration.Configuration.UseMemoryStorage();
}
RegisterServices(services);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
var requestService = app.ApplicationServices.GetService<IBudgetReleaseRequestService>();
var mailService = app.ApplicationServices.GetService<IMailer>();
_jobManager = new RecurringJobManager();
_jobManager.AddOrUpdate("Job: Dispatch Email from Queue", () => StartupMailJob(requestService, mailService), Cron.Minutely);
}
private static void RegisterServices(IServiceCollection services) => DependencyContainer.RegisterServices(services);
public static async Task StartupMailJob(IBudgetReleaseRequestService requestService, IMailer mailService)
{
try
{
var emailsToSend = await requestService.DispatchEmails();
// Send emails
foreach (var emailToSend in emailsToSend)
{
}
// Update status of sent
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
// In a project, far far away...
public static void RegisterServices(IServiceCollection services)
{
...
services.AddScoped<IBudgetReleaseRequestService, BudgetReleaseRequestService>();
...
}
How do I satisfy Hangfire's requirment of a class instance instead of an interface?
Here is the implementation that I found best worked for me:
1 Register the service I wanted to schedule in Startup.cs
services.AddScoped<IMyJob, MYJob>();
2 Created an activator class that is used to define the scope for the execution of the job.
public class HangFireActivatorMyJob : IHangFireActivatorMyJob
{
private readonly IServiceProvider _serviceProvider;
public HangFireActivatorMyJob (IServiceProvider serviceProvider)
{
this._serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
public async Task Run(IJobCancellationToken token)
{
token.ThrowIfCancellationRequested();
await RunAtTimeOf(DateTime.Now);
}
public async Task RunAtTimeOf(DateTime now)
{
using IServiceScope scope = this._serviceProvider.CreateScope();
var myJobService= scope.ServiceProvider.GetRequiredService<IMyJob>();
await myJobService.RunSomeTaskOnJob();
}
}
3 Register the activator
services.AddTransient<IHangFireActivatorMyJob, HangFireActivatorMyJob >();
4 Add the IRecurringJobManager interface to the configure method within Startup.cs
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env,
IRecurringJobManager recurringJobManager)
5 Add the job to the provided IRecurringJobManager
recurringJobManager.AddOrUpdate<HangFireActivatorMyJob >(nameof(HangFireActivatorMyJob ),
job => serviceProvider.GetRequiredService<IHangFireActivatorMyJob>()
.Run(JobCancellationToken.Null)
, Cron.Hourly(3), TimeZoneInfo.Utc);

GRPC service instantiated per call

I've created a GRPC service host under .NET core 3.1 (using Grpc.AspNetCore v2.30 from https://github.com/grpc/grpc-dotnet). By putting a breakpoint in the "ProxyService" constructor, I can see that the class is instantiated per call - every time a GRPC call is coming from a client, the breakpoint is hit. How do I configure it to always use the same ProxyService instance?
These are the program and Startup classes:
class Program
{
const int _port = 23456;
static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
Console.WriteLine("started - press any key to quit...");
Console.ReadKey();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
options.ConfigureEndpointDefaults(o =>
{
o.Protocols = HttpProtocols.Http2;
});
options.ListenAnyIP(_port);
});
webBuilder.UseStartup<Startup>();
});
}
public class ProxyService : StreamingApi.Protos.StreamingApi.StreamingApiBase
{
public ProxyService()
{
// gets here with every client call
}
public override Task<UpdateResponse> Update(UpdateRequest request, ServerCallContext context)
{
return Task.FromResult(new UpdateResponse());
}
}
class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<ProxyService>();
});
}
}
First, let me guess why you want to do this:
You have some heavy-logic inside ProxyService like initialization of some sort;
You have static variables which you want to share between calls;
To resolve first case you should use either method itself:
public ProxyService(IFooBar foobar)
{
this.foobar = foobar;
}
public override Task<UpdateResponse> Update(UpdateRequest request, ServerCallContext context)
{
await this.foobar.InitializeAsync();
return Task.FromResult(new UpdateResponse());
}
Or some other trigger in your system, like for example "At service start":
public interface IFooBarInitilizer :IHostedService
{
}
public class FooBarInitilizer : IFooBarInitilizer
{
public async Task StartAsync(CancellationToken token){ await this.foobar.InitializeAsync(); }
}
//in your Configure
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddSingleton<IFooBarInitializer, FooBarInitializer>();
services.AddHostedService(x=> x.GetService<IFooBarInitializer>());
}
For the second case it is even easier, because you just can specify your shared resources through interface dpendency:
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddSingleton<IFooBarResource, FooBarResource>();
}
public class ProxyService : StreamingApi.Protos.StreamingApi.StreamingApiBase
{
public ProxyService(IFooBarResource myStaticResource)
{
this.myStaticResource = myStaticResource;
}
public override Task<UpdateResponse> Update(UpdateRequest request, ServerCallContext context)
{
var somethingGood = this.myStaticResource.GetMeSomethingGood();
return Task.FromResult(new UpdateResponse());
}
}
According to this page you can register your grpc service yourself as a singleton.
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddSingleton(new ProxyService());
}

Passing a Hub Context to a non Controller Class

I am trying to send a message to a client in the server using SignalR
I am trying to do that in a class that is not a Controller. I have made the Startup like so:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.Configure<ConfigurationModel>(Configuration.GetSection("configurationModel"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
app.UseSignalR(routes => { routes.MapHub<MoveViewHub>("/movehub"); });
}
}
In my Program, this one:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
This is in my Hub:
public class MoveViewHub : Hub
{
private async void ReceiveTagNumber(object sender, EventArgs e)
{
await Clients.All.SendAsync("ReceivedFromServer", sender.ToString());
}
public async Task MoveViewFromServer(float newX, float newY)
{
Console.WriteLine(#"Receive position from Server app: " + newX + "/" + newY);
await Clients.Others.SendAsync("ReceivedNewPosition", newX, newY);
//await Clients.All.SendAsync("ReceivedNewPosition", newX, newY);
}
public async Task WriteThisMessage(string message)
{
Console.WriteLine(message);
await Clients.Others.SendAsync("ReceivedStatus", "Message was received. Thank you.");
}
public override Task OnConnectedAsync()
{
Console.WriteLine("Client has connected");
RfidClass rfidClass = new RfidClass("THE HUB CONTEXT SHOULD BE HERE"); ====>> I NEED TO PASS MY HUBCONTEXT
rfidClass.sas();
RfidClass.SendTagNumber += ReceiveTagNumber;
System.Diagnostics.Process.Start(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Notepad++", #"notepad++.exe"));
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
Console.Write("Client has disconnected");
return base.OnDisconnectedAsync(exception);
}
}
This is the RfidClass:
private IHubContext<MoveViewHub> hubContext;
public RfidClass(IHubContext<MoveViewHub> hubContext)
{
this.hubContext = hubContext;
}
public void sas()
{
Start();
}
private void Start()
{
try
{
hubContext.Clients.Others.SendAsync("ReceivedFromServer", "You are connected");
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
How can I make it right?
You need to inject IServiceProvider into your Hub by .NET Core DI (like into standard Controller, injecting by constructor):
public class MoveViewHub : Hub
{
private readonly IServiceProvider provider
public MovieViewHub(IServiceProvider provider)
{
this.provider = provider
}
}
Then you can do something like this:
public override Task OnConnectedAsync()
{
Console.WriteLine("Client has connected");
// you need to inject service provider to your hub, then get hub context from
// registered services
using (var scope = this.provider.CreateScope())
{
// get instance of hub from service provider
var scopedServices = scope.ServiceProvider;
var hub = scopedServices.GetRequiredService<IHubContext<MoveViewHub>>
// pass hub to class constructor
RfidClass rfidClass = new RfidClass(hub)
rfidClass.sas();
RfidClass.SendTagNumber += ReceiveTagNumber;
}
System.Diagnostics.Process.Start(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Notepad++", #"notepad++.exe"));
return base.OnConnectedAsync();
}
EDIT:
If you just want to SignalR work, you dont need to work on Hub. Instead make service. In this service inject HubContext<> of your Hub:
// you need to make your own class and interface and inject hub context
public interface ISignalRService
{
Task SendMessageToAll(string message);
}
public class SignalRService : ISignalRService
{
private readonly IHubContext<YourHub> hubContext;
public SignalRService (IHubContext<NotificationHub> hubContext)
{
this.hubContext = hubContext;
}
public async Task SendMessageToAll(string message)
{
await this.hubContext.Clients.All.SendAsync("ReciveMessage", message);
}
}
Then register that service in your Startup class:
services.AddScoped<ISignalRService, SignalRService>();
After that you can call SignalRService wherever you want to like normal service from .NetCore DI container:
private readonly ISignalRService notificationService;
public SomeController(ISignalRService notificationService)
{
this.notificationService = notificationService;
}
[HttpGet]
public async Task<IActionResult> Send()
{
await this.notificationService.SendMessageToAll("message");
return Ok();
}
You dont need to make some work around like RfidClass.

Overriding database provider in integration test with WebApplicationFactory

I am following the official MS documentation for integration testing .Net Core (https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.1).
I was able to get the first part of the integration test done where I was not overriding the startup class of the application I am testing (i.e. I was using a web application factorythat did not override any services).
I want to override the database setup to use an in-memory database for the integration test. The problem I am running into is that the configuration continues to try and use the sql server for services.AddHangfire().
How do I override only above specific item in my integration test? I only want to override the AddHangfire setup and not services.AddScoped<ISendEmail, SendEmail>(). Any help would be appreciated.
Test Class with the custom web application factory
public class HomeControllerShouldCustomFactory : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<Startup> _factory;
public HomeControllerShouldCustomFactory(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = factory.CreateClient();
}
[Fact]
public async Task IndexRendersCorrectTitle()
{
var response = await _client.GetAsync("/Home/Index");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
Assert.Contains("Send Email", responseString);
}
}
Custom Web Application Factory
public class CustomWebApplicationFactory<TStartup>: WebApplicationFactory<SendGridExample.Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Create a new service provider.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
var inMemory = GlobalConfiguration.Configuration.UseMemoryStorage();
services.AddHangfire(x => x.UseStorage(inMemory));
// Build the service provider.
var sp = services.BuildServiceProvider();
});
}
}
My startup.cs in my application that I am testing
public IConfiguration Configuration { get; }
public IHostingEnvironment Environment { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("ASP_NetPractice")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddScoped<ISendEmail, SendEmail>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseHangfireServer();
app.UseHangfireDashboard();
RecurringJob.AddOrUpdate<ISendEmail>((email) => email.SendReminder(), Cron.Daily);
app.UseMvc();
Update
I don't see this issue in my other example project where I am using only entity framework. I have a simple application with an application db context which uses SQL server. In my test class, I override it with an in-memory database and everything works. I am at a loss at to why it will work in my example application but not work in my main application. Is this something to do with how HangFire works?
In my test application (example code below), I can delete my sql database, run my test, and the test passes because the application DB context does not go looking for the sql server instance but uses the in-memory database. In my application, the HangFire service keeps trying to use the sql server database (if I delete the database and try to use an in-memory database for the test - it fails because it can't find the instance its trying to connect to). How come there is such a drastic difference in how the two projects work when a similar path is used for both?
I ran through the debugger for my integration test which calls the index method on the home controller above (using the CustomWebApplicationFactory). As I am initializing a test server, it goes through my startup class which calls below in ConfigureServices:
services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("ASP_NetPractice")));
After that, the Configure method tries to call below statement:
app.UseHangfireServer();
At this point the test fails as It cannot find the DB. The DB is hosted on Azure so I am trying to replace it with an in-memory server for some of the integration test. Is the approach I am taking incorrect?
My example application where its working
Application DB Context in my example application
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public virtual DbSet<Message> Messages { get; set; }
public async Task<List<Message>> GetMessagesAsync()
{
return await Messages
.OrderBy(message => message.Text)
.AsNoTracking()
.ToListAsync();
}
public void Initialize()
{
Messages.AddRange(GetSeedingMessages());
SaveChanges();
}
public static List<Message> GetSeedingMessages()
{
return new List<Message>()
{
new Message(){ Text = "You're standing on my scarf." },
new Message(){ Text = "Would you like a jelly baby?" },
new Message(){ Text = "To the rational mind, nothing is inexplicable; only unexplained." }
};
}
}
Startup.cs in my example application
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
CustomWebApplicationFactory - in my unit test project
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Create a new service provider.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Add a database context (ApplicationDbContext) using an in-memory
// database for testing.
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
options.UseInternalServiceProvider(serviceProvider);
});
// Build the service provider.
var sp = services.BuildServiceProvider();
});
}
}
My unit test in my unit test project
public class UnitTest1 : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<Startup> _factory;
public UnitTest1(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = factory.CreateClient();
}
[Fact]
public async System.Threading.Tasks.Task Test1Async()
{
var response = await _client.GetAsync("/");
//response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
Assert.Contains("Home", responseString);
}
Update 2
I think I found an alternate to trying to override all my configuration in my integration test class. Since it's a lot more complicated to override HangFire as opposed to an ApplicationDBContext, I came up with below approach:
Startup.cs
if (Environment.IsDevelopment())
{
var inMemory = GlobalConfiguration.Configuration.UseMemoryStorage();
services.AddHangfire(x => x.UseStorage(inMemory));
}
else
{
services.AddHangfire(x => x.UseSqlServerStorage(Configuration["DBConnection"]));
}
Then in my CustomWebApplicationBuilder, I override the environment type for testing:
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<SendGridExample.Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Development"); //change to Production for alternate test
builder.ConfigureServices(services =>
{
// Create a new service provider.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
});
}
}
With that approach, I don't need to worry about having to do extra logic to satisfy hangfire's check for an active DB. It works but I am not 100% convinced its the best approach as I'm introducing branching in my production startup class.
There are two different scenarios you need to check.
Create a job by class BackgroundJob
Create a job by interface IBackgroundJobClient
For the first option, you could not replace the SqlServerStorage with MemoryStorage.
For UseSqlServerStorage, it will reset JobStorage by SqlServerStorage.
public static IGlobalConfiguration<SqlServerStorage> UseSqlServerStorage(
[NotNull] this IGlobalConfiguration configuration,
[NotNull] string nameOrConnectionString)
{
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
if (nameOrConnectionString == null) throw new ArgumentNullException(nameof(nameOrConnectionString));
var storage = new SqlServerStorage(nameOrConnectionString);
return configuration.UseStorage(storage);
}
UseStorage
public static class GlobalConfigurationExtensions
{
public static IGlobalConfiguration<TStorage> UseStorage<TStorage>(
[NotNull] this IGlobalConfiguration configuration,
[NotNull] TStorage storage)
where TStorage : JobStorage
{
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
if (storage == null) throw new ArgumentNullException(nameof(storage));
return configuration.Use(storage, x => JobStorage.Current = x);
}
Which means, no matter what you set in CustomWebApplicationFactory, UseSqlServerStorage will reset BackgroundJob with SqlServerStorage.
For second option, it could replace IBackgroundJobClient with MemoryStorage by
public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.AddSingleton<JobStorage>(x =>
{
return GlobalConfiguration.Configuration.UseMemoryStorage();
});
});
}
}
In conclusion, I suggest you register IBackgroundJobClient and try the second option to achieve your requirement.
Update1
For DB is not available, it could not be resolved by configuring the Dependency Injection. This error is caused by calling services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("ASP_NetPractice")));.
For resolving this error, you need to overriding this code in Startup.cs.
Try steps below:
Change Startup to below:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//Rest Code
ConfigureHangfire(services);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//Rest Code
app.UseHangfireServer();
RecurringJob.AddOrUpdate(() => Console.WriteLine("RecurringJob!"), Cron.Minutely);
}
protected virtual void ConfigureHangfire(IServiceCollection services)
{
services.AddHangfire(config =>
config.UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection"))
);
}
}
Create StartupTest in test project.
public class StartupTest : Startup
{
public StartupTest(IConfiguration configuration) :base(configuration)
{
}
protected override void ConfigureHangfire(IServiceCollection services)
{
services.AddHangfire(x => x.UseMemoryStorage());
}
}
CustomWebApplicationFactory
public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint> where TEntryPoint: class
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost.CreateDefaultBuilder(null)
.UseStartup<TEntryPoint>();
}
}
Test
public class HangfireStorageStartupTest : IClassFixture<CustomWebApplicationFactory<StartupTest>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<StartupTest> _factory;
public HangfireStorageStartupTest(CustomWebApplicationFactory<StartupTest> factory)
{
_factory = factory;
_client = factory.CreateClient();
}
}

Asp.Net Core - Using Session variables in task

I'm building a simulator with Asp.Net application where simulation is done as a separate task. I'm having problem with accessing data which is processed in this task.
I've tried to access task or threat I created, but don't see a way to track created task.
I've also tried to use Session, but after request is completed I have no access to Session anymore, so background task stops with error.
LatheController:
public class LatheController : Controller
{
private readonly ApiDbContext _dbContext;
private static ILatheService _latheService;
public LatheController(ApiDbContext dbContext)
{
_dbContext = dbContext;
}
[HttpGet]
public LatheCell GetLatheCell()
{
if (_latheService == null)
{
_latheService = new LatheService();
_latheService.Start(lc);
}
return _latheService.GetLatheCell();
}
[HttpGet("{id}")]
public LatheCell GetLatheCell([FromRoute]int id)
{
return AppHttpContext.Current.Session.GetObjectFromJson<LatheCell>("latheCell");
}
}
LatheService:
public class LatheService : ILatheService
{
private LatheSimulator _latheSim;
public void Start(LatheCell lc)
{
_latheSim = new LatheSimulator(lc);
Task task = new Task( () => { _latheSim.Start(); });
task.Start();
}
public LatheCell GetLatheCell()
{
return _latheSim.GetLatheCell();
}
}
LatheSimulator:
public class LatheSimulator
{
private LatheCell _latheCell;
private bool _keepRunning;
public LatheSimulator(LatheCell latheCell)
{
_latheCell = latheCell;
}
public void Start()
{
_keepRunning = true;
Simulation();
}
public void Stop()
{
_keepRunning = false;
}
public LatheCell GetLatheCell()
{
return _latheCell;
}
private void Simulation()
{
while (_keepRunning)
{
_latheCell.RunningCycle++;
//// The simuation ///
AppHttpContext.Current.Session.SetObjectAsJson("latheCell", _latheCell);
//Sleep operation to simulate speed of the conveyor
System.Threading.Thread.Sleep(_latheCell.ConveyorIn.Speed);
}
}
}
Startup:
public void ConfigureServices(IServiceCollection services)
{
/// ... some other configurations
services.AddMvc();
services.AddDistributedMemoryCache();
services.AddSession();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
///....
app.UseSession();
AppHttpContext.Services = app.ApplicationServices;
/// .....
}
AppHttpContext:
public static class AppHttpContext
{
static IServiceProvider services = null;
public static IServiceProvider Services
{
get { return services; }
set
{
if (services != null)
{
throw new Exception("Can't set once a value has already been set.");
}
services = value;
}
}
public static HttpContext Current
{
get
{
IHttpContextAccessor httpContextAccessor = services.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
return httpContextAccessor?.HttpContext;
}
}
}
I know I could use database for this, but because I will be calling database every 5 sec in the simulation, I would like to keep simulator in the memory for the performance.
If anyone could put me in the right direction I will be very appreciated. I'm scratching my head for several days now.
I would like to keep simulator in the memory for the performance.
The problem is if App Pool recycles and App Domain restarts, you will loose everything that task is running.
Ideally, we need to storage data in persistent storage like SQL Server. I'll say calling database every 5 sec is not a big deal, unless you are querying a lot of data to process.
We normally use background task such as hangfire. You could read more about other background tasks at Scott Hanselman's How to run Background Tasks in ASP.NET.

Categories