How to configure Hangfire with Autofac in a dotnet core console app - .net-core

I'm trying to port a working Hangfire setup embedded in a Kestrel webserver to a console app. I've modified the web app so it still provides the Hangfire dashboard but doesn't start its own Hangfire server.
The code I must port uses Autofac. I've added the Hangfire.Autofac package to the console app and have already performed all the steps detailed in the answer to Hangfire Autofac .net core 3.1
When I create a job (using the web app) the console app Hangfire server tries to execute the job but I get this failure message:
The requested service 'AED.ServicesLayer.JobProcessing.ProcessManager' has not been registered.
Investigating this we examine the setup of Autofac in the console app. This is how I set up my container.
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();
var containerBuilder = new Autofac.ContainerBuilder();
containerBuilder.RegisterInstance(Log.Logger).AsImplementedInterfaces();
containerBuilder.RegisterModule(new RepositoryModule(config));
containerBuilder.RegisterType<UserService>().As<IUserService>();
containerBuilder.RegisterInstance(config).As<IConfiguration>();
containerBuilder.RegisterModule(new JobProcessingModule(config));
var container = containerBuilder.Build();
When the app is executed, hitting a breakpoint in JobProcessingModule proves the following line of code is executed.
builder.RegisterType<ProcessManager>().As<IProcessManager>();
It is very curious that the containerBuilder instance passed to JobProcessingModule.Load(containerBuilder) is not the same containerBuilder object on which RegisterModule is invoked.
However, experiments with simplified injectables suggest that this is normal, and the injected items are nevertheless visible in the registrations for the container that is returned.
Re-examining the logged failure we note that the class is mentioned by class name and not by interface. Changing the registration by removing the interface registration, like so
builder.RegisterType<ProcessManager>();//.As<IProcessManager>();
caused the ProcessManager to be found in the Hangfire console host but caused run-time errors in the web application when creating the job.
Registering it both ways caused ProcessManager to be found by both, with a new problem surfacing: cannot resolve dependencies. This, however, is merely a new case of the same problem.
While this allows me to move forward with getting a console host working, I do not like code I do not understand. Why should the console host require registration by class name when the web app does not?
Whatever is causing this has also caused Hangfire.IBackgroundJobClient to fail to resolve to the background job client. This is a hangfire class so it really does seem like there is a fundamental problem.

A lengthy investigation eventually revealed, confirmed by experiments, that this code
_recurringJobManager.AddOrUpdate(
insertResult.ToString(),
pm => pm.RunScheduledJobs(insertResult), interval.CrontabExpression
);
is responsible for the behaviour described in the question. AddOrUpdate is a generic method. When it is not explicitly typed it acquires its type from the class of the object passed to it. When the method is explicitly typed as the interface, like so
_recurringJobManager.AddOrUpdate<IProcessManager>(
insertResult.ToString(),
pm => pm.RunScheduledJobs(insertResult), interval.CrontabExpression
);
it remains compatible with the object, but the type acquired by Hangfire is the interface, and the console application can resolve ProcessManager from its interface.
Why the problem was not manifest in the web hosted Hangfire server remains a puzzle, but at least now I'm puzzled by the absence of a problem in a situation I don't have.

Related

The configured execution strategy 'SqlRetryingExecutionStrategy' does not support user-initiated transactions

I have ASP.Net 4.7.2 window service which is processing NServiceBus messages. Currently it is deployed to On-Premise server. It has retry mechanism as well and working fine. Now I am going to containerizing it. While running into docker window container, it is doing SQL operation using Entity framework and giving exception as mentioned below:
The configured execution strategy 'SqlRetryingExecutionStrategy' does not support user-initiated transactions. Use the execution strategy returned by 'DbContext.Database.CreateExecutionStrategy()' to execute all the operations in the transaction as a retriable unit.
While running locally by installing manually or on On-Premise server, it is working fine but in container it is throwing exception.
Can any one help me what can be the root cause?
It sounds like the piece of code does manual transaction management and is not wrapped within an execution strategy execute.
if your code initiates a transaction using BeginTransaction() you are defining your own group of operations that need to be treated as a unit, and everything inside the transaction would need to be played back shall a failure occur.
The solution is to manually invoke the execution strategy with a delegate representing everything that needs to be executed. If a transient failure occurs, the execution strategy will invoke the delegate again.
https://learn.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency#execution-strategies-and-transactions
using var db = new SomeContext();
var strategy = db.Database.CreateExecutionStrategy();
strategy.Execute(
() =>
{
using var context = new SomeContext();
using var transaction = context.Database.BeginTransaction();
context.SaveChanges();
transaction.Commit();
});
``

.NET Generic Host - Is it possible to stop and restart a host?

Consider this extremely simple .NET Core 3.1 (and .NET 5) application with no special config or hosted services:
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
internal class Program
{
public static async Task Main(string[] args)
{
var builder = Host.CreateDefaultBuilder(args);
builder.UseWindowsService();
var host = builder.Build();
var fireAndforget = Task.Run(async () => await host.RunAsync());
await Task.Delay(5000);
await host.StopAsync();
await Task.Delay(5000);
await host.RunAsync();
}
The first Run (sent as a background fire and forget task only for the purpose of this test) and Stop complete successfully. Upon calling Run a second time, I receive this exception:
System.AggregateException : 'Object name: 'EventLogInternal'.Cannot access a disposed object. Object name: 'EventLogInternal'.)'
If I do the same but using StartAsync instead of RunAsync (this time no need for a fireAndForget), I receive a System.OperationCanceledException upon called StartAsync the second time.
Am I right to deduce that .NET Generic Host aren't meant to be stopped and restarted?
Why do I need this?
My goal is to have a single application running as a Windows Service that would host two different .NET Generic Host. This is based on recommendation from here in order to have separate configuration and dependency injection rules and message queues.
One would stay active for all application lifetime (until the service is stopped in the Windows services) and would serve as a entry point to receive message events that would start/stop the other one which would be the main processing host with full services. This way the main services could be in "idle" state until they receive a message triggering their process, and another message could return them to idle state.
The host returned by CreateDefaultBuilder(...).Build() is meant to represent the whole application. From docs:
The main reason for including all of the app's interdependent resources in one object is lifetime management: control over app startup and graceful shutdown.
The default builder registers many services in singleton scope and when the host is stopped all of these services are disposed or switched to some "stopped" state. For example before calling StopAsync you can resolve IHostApplicationLifetime:
var appLifetime = host.Services.GetService<IHostApplicationLifetime>();
It has cancellation tokens representing application states. When you call StartAsync or RunAsync after stopping, all tokens still have IsCancellationRequested set to true. That's why the OperactionCancelledException is thrown in Host.StartAsync.
You can list other services during configuration:
For me it sounds like you just need some background jobs to process messages but I've never used NServiceBus so I don't know how it will work with something like Hangfire. You can also implement IHostedService and use it in the generic host builder.
I'm doing something like:
do
{
using IHost host = BuildHost();
await host.RunAsync();
} while (MainService.Restart);
with MainService constructor:
public MainService(IHostApplicationLifetime HostApplicationLifetime)
MainService.Restart is a static bool set by the MainService itself in response to some event which also calls HostApplicationLifetime.StopApplication().

ASP.NET Web API Startup Questions

I am creating an ASP.NET Web API using .NET 4.5.2. The API needs to connect to a runspace on startup. I have questions about when this Startup.Configuration method actually runs though. It does not seem to run when I start the website or app pool. It seems to wait until the first time somebody tries to access the website. Is this correct? Also, it seems to run again at random times. I have seen it run after 2 hours, 4 hours, and 16 hours. It doesn't really make any sense. Can somebody clear up for me when these methods should run? Also, if you have a suggestion for a better place to put them given that I want it to be a shared runspace for all connections and that I want it to run before anybody tries to connect to the API. Perhaps a separate service?
Also, would it be worth looking into ASP.NET CORE? I don't need it to run on anything other than IIS, however if there is a benefit to using CORE I am at a point where it will be easy to switch.
public partial class Startup
{
public Cache GlobalCache;
public static PowershellRunspace PSRunspace;
public static ActiveDirectory ADObjects = new ActiveDirectory();
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
GlobalCache = new Cache();
AppLog log = new AppLog();
log.InfoLog("Starting PowerShell Runspace in Hangfire...", true);
GlobalConfiguration.Configuration.UseSqlServerStorage("Hangfire");
BackgroundJob.Enqueue(() => log.InfoLog("Hangfire started!", true));
BackgroundJob.Enqueue(() => ADObjects.Startup(true));
BackgroundJob.Enqueue(() => StaticRunspace.Start());
app.UseHangfireDashboard();
app.UseHangfireServer();
}
}
Assuming you're running this application in IIS (and not self-hosting), the following rules apply:
The Configuration method runs once per application start.
The application is started lazily (on the first request to it via HTTP/S).
IIS has a few settings that affect the application:
An idle timeout. If the app isn't accessed via a request in 20 minutes then the application is unloaded / put offline. The next request starts it again.
A regular app pool recycle. It just straight up restarts the application by recycling the app pool every 1740 minutes.
So the behavior you're seeing is likely due to the infrequent access of the application, combined with the IIS defaults. If you'd like to see or configure the settings, you can do so by going into IIS, right clicking on your app pool, and selecting Advanced Settings.

How to give a web request to windows task Scheduler

I have a static webmethod i.e (http://localhost:61176/trunk/MusteriKontrol.aspx/CheckMusteri) I want to call this method from Windows Task Scheduler. How should I do this?
You could use a PowerShell script. This has a check for the time, a commented-out Try..Catch in case you want to do something if an error is raised from the query, and records when it has run in the Application event log:
# Download the HTML of a web page.
# Make sure an event log source is created with New-EventLog -LogName Application -Source MyPSscript
# Only do this if the time is 5a.m. or later, to give the server a rest from midnight.
$currHour = (Get-Date).Hour
if ($currHour -ge 5) {
$web = New-Object Net.WebClient
#try {
$temp = $web.DownloadString("http://localhost:61176/trunk/MusteriKontrol.aspx/CheckMusteri")
#}
#catch {
# do nothing.
#}
write-eventlog -logname Application -source MyPSscript -eventID 1001 -entrytype Information -message "Fetched web page." -category 0
}
Have considered a different alternative to scheduling the call of a web method from Windows Task Scheduler?
For example, scheduling tasks from within an ASP.NET project is possible using the Revalee open source project.
Revalee is a service that allows you to schedule web callbacks to your web application. In your case, you would schedule a callback that would call your web method at a specific time. Revalee works very well with tasks that are discrete transactional actions, like updating some database values or sending an automated email message (read: not long running). The upside is that the code that schedules the callback, as well as the code that performs your action (i.e., your web method), would all reside within your app.
To use Revalee, you would:
Install the Revalee Service, a Windows Service, on your server. The Windows Service is available in the source code (which you would compile yourself) or in a precompiled version available at the Revalee website.
Use the Revalee client library in your Visual Studio project. (There is an MVC-specific version too.) The client library is available in the source code (which, again, you would compile yourself) or in a precompiled version available via NuGet.
You would register a future callback when your code calls the ScheduleWebMethodCallback() method (this example is assuming that you need your action to run 12 hours from now).
private void ScheduleWebMethodCallback()
{
DateTimeOffset callbackTime = DateTimeOffset.Now.AddHours(12.0);
// The callback should be in 12 hours from now
Uri callbackUrl = new Uri(string.Format("http://localhost:61176/trunk/MusteriKontrol.aspx/CheckMusteri"));
// Register the callback request with the Revalee service
RevaleeRegistrar.ScheduleCallback(callbackTime, callbackUrl);
}
When Revalee calls your application back, your app would perform whatever action you have coded it to do in the web method you have listed above.
In case it was not clear above, the Revalee Service is not an external 3rd party online scheduler service, but instead a Windows Service that you install and fully control on your own network. It resides and runs on a server of your own choosing, most likely your web server (but this is not a requirement), where it can receive callback registration requests from your ASP.NET application.
I hope this helps.
Note: The code example above uses a synchronous version of ScheduleCallback(), the Revalee client library also supports asynchronous calls Ă  la:
RevaleeRegistrar.ScheduleCallbackAsync(callbackTime, callbackUrl);
Disclaimer: I was one of the developers involved with the Revalee project. To be clear, however, Revalee is free, open source software. The source code is available on GitHub.

servlet initialization failure in websphere 6.0

I have many servlets in a web applicaton; for some stange reason, only and only one of them always fails in initialization with the following error trace:-
00000045 ServletWrappe E SRVE0100E: Did not realize init() exception thrown by servlet MyServletX: java.lang.NullPointerException
at com.ibm.ws.webcontainer.WebAppPmiListener.onServletStartInit(WebAppPmiListener.java:120)
at com.ibm.ws.webcontainer.webapp.FireOnServletStartInit.fireEvent(WebAppEventSource.java:237)
at com.ibm.ws.webcontainer.util.EventListeners.fireEvent(EventListeners.java:48)
at com.ibm.ws.webcontainer.webapp.WebAppEventSource.onServletStartInit(WebAppEventSource.java:105)
at com.ibm.ws.webcontainer.servlet.ServletWrapper.init(ServletWrapper.java:261)
at com.ibm.ws.webcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:444)
at com.ibm.ws.webcontainer.webapp.WebApp.handleRequest(WebApp.java:2841)
at com.ibm.ws.webcontainer.webapp.WebGroup.handleRequest(WebGroup.java:220)
at com.ibm.ws.webcontainer.VirtualHost.handleRequest(VirtualHost.java:204)
at com.ibm.ws.webcontainer.WebContainer.handleRequest(WebContainer.java:1681)
at com.ibm.ws.webcontainer.channel.WCChannelLink.ready(WCChannelLink.java:77)
at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleDiscrimination(HttpInboundLink.java:421)
at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleNewInformation(HttpInboundLink.java:367)
at com.ibm.ws.http.channel.inbound.impl.HttpICLReadCallback.complete(HttpICLReadCallback.java:94)
at com.ibm.ws.tcp.channel.impl.WorkQueueManager.requestComplete(WorkQueueManager.java:548)
at com.ibm.ws.tcp.channel.impl.WorkQueueManager.attemptIO(WorkQueueManager.java:601)
at com.ibm.ws.tcp.channel.impl.WorkQueueManager.workerRun(WorkQueueManager.java:934)
at com.ibm.ws.tcp.channel.impl.WorkQueueManager$Worker.run(WorkQueueManager.java:1021)
at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1332)
I could not figure out if there is anything extra ordinary with this servlet. There is no init() method in this servlet and it extends HTTPServlet. Any idea what could be reason? I am using websphere server 6.0.x. How to get more debugging information in this case?
Well I don't know still cause of above error, but this is how it started working strangely:- i) Re-applied recommended fixes by IBM for my WAS version (especially there are IBM JDK upgrade related fix patches) ii) created a new profile of server iii) Install web application to new profile and it started working.
I don't think this is a product issue.
To debug this problem what i would suggest is to place a simple servlet (kind of Hello World) and deploy it to the server and see what happens.
initialization does not necessarily mean init() method alone.
If you have a static block in your servlet, if you have any variables that are initialized they would all be part of the initialization activity.
Look at the FFDC logs that were generated when this error occurred and that should provide you with clues.
As bkail mentioned, also ensure that yo have the latest fixpacks just to eliminate known problems with the product.
if the hello world servlet works, suggest you place hte servlet code here along with the SystemOut and System Err logs that correspond to this issue along with the relevant FFDC logs and i am sure most of us will be able to help you out with this
HTH
Manglu

Resources