Adding health checks with AddHealthChecks().AddCheck() prevents calls to IHeathCheckPublisher.PublishChecks - c#

Given the following code, I see calls to IHealthCheckPublisher.PublishAsync:
await Host.CreateDefaultBuilder()
.ConfigureLogging(loggerBuilder =>
{
loggerBuilder.AddConsole();
})
.ConfigureServices((hostContext, services) => {
services.AddHealthChecks();
services.AddSingleton<IHealthCheckPublisher, SimpleHealthCheckPublisher>();
}).RunConsoleAsync();
and:
internal class SimpleHealthCheckPublisher : IHealthCheckPublisher
{
private readonly ILogger<SimpleHealthCheckPublisher> logger;
public SimpleHealthCheckPublisher(ILogger<SimpleHealthCheckPublisher> logger)
{
this.logger = logger;
}
public Task PublishAsync(HealthReport report, CancellationToken cancellationToken)
{
logger.LogInformation("HealthReport received: {Status}", report.Status);
return Task.CompletedTask;
}
}
I see the following output:
HealthReport received: Healthy
However, if I add a trvial health check, IHealthCheckReport.PublishAsync is never called:
services.AddHealthChecks()
.AddCheck("test", () => HealthCheckResult.Healthy("We're good"));
How do I add a health check but still get IHealthCheckReport.PublishAsync to be called?
Full repro available here:https://github.com/anvilcloud/HealthChecksRepro
Update #1:
This works by setting the TargetFramework to net7.0. Unsure why. Unfortunately, I need it to work for net6.0.

Downgrading the HealthChecks packages worked for me:
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="6.0.14" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.14" />

Related

.NET 6 Windows Service Logs Two Duplicate Events At Shutdown

Let me start by stating that this issue does not seem to cause any problems but it certainly doesn't look correct so I thought I would inquire about a fix.
I have created a Windows service with .NET 6 loosely following the guide here. The biggest difference is that I have stripped out all of the code in the ExecuteAsync method so, basically, my service does not actually do anything at the moment.
When I stop the service, I notice that two service stopped events are written to the event log (see image). The two events logged at 9:28:48 PM seem to be duplicates. Can anyone explain why this event is duplicated and how to resolve?
UPDATE
Here are the two code files in my project:
Program.cs
namespace WindowsServiceSandbox
{
public class Program
{
public static void Main(string[] args)
{
var host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options => {options.ServiceName = "Test Windows Service";})
.ConfigureServices(services => {services.AddHostedService<Worker>();})
.Build();
host.Run();
}
}
}
Worker.cs
namespace WindowsServiceSandbox
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
return Task.CompletedTask;
}
}
}

UseExceptionHandler Blazor server-side

Could someone confirm that the app.UseExceptionHandler() does not work for server-side blazor?
I have seen several cases where my custom ErrorHandler does not catch exceptions being thrown by my application. Example code
Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
...
app.UseExceptionHandler(new ExceptionHandlerOptions { ExceptionHandler = ErrorHandler.HandleError });
...
}
ErrorHandler.cs:
public static async Task HandleError(HttpContext context)
{
var error = context.Features.Get<IExceptionHandlerFeature>()?.Error;
var message = error?.Message ?? "[EXCEPTION NOT FOUND]";
return;
}
An example are when my repository are throwing an exception as such:
The instance of entity type cannot be tracked because another instance with the same key value for {'Id'} is already being tracked
My MVC solution are catching all exceptions and it is using similar ErrorHandling implementations.
Indeed lots of ASP Core middleware scenarios will not work completely in a server-side Blazor application.
This is because Blazor works with SignalR and the Blazor Hub.
You will see that when starting a Blazor application there are first a few HTTP requests that will pass the pipeline till the end. (in most cases these are the initial page load and then a negotiation phase).
But then comes a request to "/_blazor" and this is the moment that the connection stays open to continue communication through websockets.
If you have an exception after this phase it will not enter your exception handler.
You can observe this by creating a small middleware class that is registered through the UseMiddleware extension method on IApplicationBuilder .
Such a middleware class requires an Invoke method like this for example:
.....
public class TestMiddleware
{
private readonly RequestDelegate _next;
public TestMiddleware(RequestDelegate next)
{
_next = next
}
public async Task Invoke(HttpContext context)
{
await _next(context);
}
....
If you put a breakpoint in the Invoke, you will notice that you will not step further once the context parameter is for path "/_blazor" .
Here is another link that discusses similar problems, different from the exception handler one but also related to the ASP.NET middleware:
https://github.com/aspnet/SignalR/issues/1334
Thanks to #reno answer , i just completed his answer
follow just 3 steps :
1.Creat an ExceptionMiddleware :
public class ExceptionMiddleware
{
public readonly RequestDelegate _next;
string _path;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(ILogger<ExceptionMiddleware> logger, RequestDelegate next,string Path)
{
_next = next;
_path = Path;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (System.Exception ex)
{
_logger.LogError(ex,$"RequsetPath: {context.Request.Path}",default);
context.Response.Redirect(_path);
}
}
}
2.create a custom MiddlewareExtension:
public static class MiddlewareExtensions
{
public static IApplicationBuilder UsemycustomException(
this IApplicationBuilder builder,string path)
{
return builder.UseMiddleware<ExceptionMiddleware>(path);
}
}
3.in Configure use this middleware as first middleware:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UsemycustomException("/Error");
.
.
.
}
note : The Error.razor file in Pages Must have the route path :
#page "/error"
see the Outputwindow Logging in vs:
You can catch any exception including blazor signalR exceptions
using ErrorBoundary on larger scope
For Example in MainLayout.razor :
<main>
<article class="content px-4">
<ErrorBoundary #ref="boundary">
<ChildContent >
#Body
</ChildContent>
<ErrorContent Context="ex">
Your Custom Error Handling Goes Here
</ErrorContent>
</ErrorBoundary>
</article>
</main>
It's important to recover the exception to init state so you don't stuck on exception loop
And in code behind MainLayout.razor.cs or #code section :
ErrorBoundary? boundary;
protected override void OnParametersSet()
{
boundary?.Recover();
}

Why do I get this InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Http.RequestDelegate'?

I've just upgraded my ASP.NET Core WebApi project from .NET Core 2.2 to 3.1.
I've fixed up all of the compile-time errors, upgraded my Nuget packages, and I can now run the app.
However, When I call Build() on my IHostBuilder, I get the following exception:
InvalidOperationException: Unable to resolve service for type
'Microsoft.AspNetCore.Http.RequestDelegate' while attempting to
activate 'MyProject.Api.Middleware.ExceptionHandlerMiddleware'.
The Middleware it's referring to is pretty standard.
ExceptionHandlerMiddleware.cs
public class ExceptionHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlerMiddleware> _logger;
public ExceptionHandlerMiddleware(RequestDelegate next, ILogger<ExceptionHandlerMiddleware> logger)
{
_logger = logger;
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
// redacted
}
}
The rest of my app initialisation is fairly standard and I didn't change much going from 2.2 to 3.1 (2.2 was working).
I did change from services.AddMvc() to services.AddControllers().
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
private static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(builder =>
{
builder.UseSerilog().UseStartup<Startup>();
})
.ConfigureLogging((context, logging) =>
{
logging
.AddConfiguration(context.Configuration.GetSection("Logging"))
.AddConsole()
.AddDebug();
});
}
}
It's also worth mentioning that the ConfigureServices() method in Startup.cs is being called and runs fine, but Configure() never runs. The Build() method always kills the app before it gets to Configure().
My Startup's Configure method signature looks like this:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
I ran into the same problem today and resolved it as follows:
In .net core 2.2 I added the middleware to the application builder and also added the middleware to the service collection. Apparently adding the middleware to the service collection is no longer required and results in the exception you posted.
In my case removing the line
services.AddSingleton<MyMiddleware>();
resolved the issue.
The solution for this problem contains two main elements:
.NET Core 3.0 introduced a change regarding service provider validation.
My code is registering too many classes, causing the validation to fail.
The solution to my problem was to introduce the following code in Program.cs:
private static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.UseDefaultServiceProvider(opt =>
{
// this overrides the default service provider options
// so that it doesn't validate the service collection (which raises exceptions)
})
[ ... ]
}
Thanks to other answers that directed my attention to my Startup.cs.
Full explanation
I use Scrutor to scan assemblies and auto-register classes. Scrutor is finding and registering my Middleware classes such as ExceptionHandlerMiddleware.
It was doing this in .NET Core 2.2 as well as 3.1. So why did it only break in Core 3.1?
Because .NET Core 3.0 introduced the Generic Host as the new default way to build a host.
The code now contains a part to enable ValidateOnBuild by default under the development environment. This caused my ServiceProvider to validate on build. And it couldn't resolve RequestDelegate because I didn't register that.
.UseDefaultServiceProvider((context, options) =>
{
var isDevelopment = context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
});
Here is the answer for those who use .netCore 6 :
First Remove Property of RequestDelegate :
private readonly RequestDelegate _next;
And then remove Your Constructor for RequestDelegate :
public SomeClassName (RequestDelegate next)
{
_next = next;
}
finally , here is the Invoke method :
public class SomeMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
Console.WriteLine("Hi I am Middleware in .net6");
await next(context);
}
}
As #user1796440 said,I could reproduce your issue by using services.AddSingleton<MyMiddleware>();.
To fix it,you need register middleware like below:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseMiddleware<ExceptionHandlerMiddleware>();
//...
}

New ILogger instance for library from IHostedService

I have a project implementing .Net Core hosting pattern.
So I have a host builder:
public static async Task Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
...
})
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<IHostedService, SkillsService>();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
});
await builder.RunConsoleAsync();
}
and a service:
public class SkillsService : IHostedService, IDisposable
{
...
public SkillsService(ILogger<SkillsService> logger)
{
...
}
public async Task StartAsync(CancellationToken cancellationToken)
{
...
}
public Task StopAsync(CancellationToken cancellationToken)
{
...
}
public void Dispose()
{
...
}
}
Now, in the StartAsync method, I want to create an instance of a class from my own library and that class is also implementing the ILogger interface. So I need to create a new instance of the Logger with the configuration from the IHostBuilder, but with the category matching the full namespace name of that library, and inject such logger to the class. I've found a lot about creating new LoggerFactory instance and configuring it, but I want to reuse the one already set up by the HostBuilder. How should I do that? There is no LoggerFactory inside the IHostedService.
For logging, I am using Microsoft.Extensions.Logging namespace.
Thanks for any bits of advice.
George
To the best of my knowledge, I had to install some nuggets to enhance Microsoft.Extensions.Logging
Serilog
Serilog.Extensions.Logging
and if you want to store the log into a file
Serilog.Extensions.Logging.File
Let me know if this tip, solve your problem.
Regards.

NullReferenceException in DefaultUserSession when loading project with IdentityServer4

I am running a dotnet core 2.2 app with IdentityServer4 installed using Nuget. When I build a docker container and run, all works fine. When I deploy this container to my Google Kubernetes Engine cluster it fails on startup with the following:
{
insertId: "18ykz2ofg4ipm0"
labels: {
compute.googleapis.com/resource_name: "fluentd-gcp-v3.1.0-nndnb"
container.googleapis.com/namespace_name: "my_namespace"
container.googleapis.com/pod_name: "identity-deployment-5b8bd8745b-qn2v8"
container.googleapis.com/stream: "stdout"
}
logName: "projects/my_project/logs/app"
receiveTimestamp: "2018-12-07T21:09:25.708527406Z"
resource: {
labels: {
cluster_name: "my_cluster"
container_name: "app"
instance_id: "4238697312444637243"
namespace_id: "my_namespace"
pod_id: "identity-deployment-5b8bd8745b-qn2v8"
project_id: "my_project"
zone: "europe-west2-b"
}
type: "container"
}
severity: "INFO"
textPayload: "System.NullReferenceException: Object reference not set
to an instance of an object.
at
IdentityServer4.Services.DefaultUserSession.RemoveSessionIdCookieAsync()
at
IdentityServer4.Services.DefaultUserSession.EnsureSessionIdCookieAsync()
at
IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)
"
timestamp: "2018-12-07T21:09:17Z"
}
As I mentioned, this works perfectly locally, and when running inside a docker container, only when within Kubernetes do I see these errors.
I'm not sure what I've missed here with kubernetes, but any help very much appreciated.
This vexed me in the last few days. I suspect it has something to do with the recent deprecation of the former mechanism in app builder configuration:
app.UseAuthentication().UseCookieAuthentication(); <-- no longer valid and apparently will not even compile now.
This has been replaced the following in the ConfigureServices section:
services.AddAuthentication("YourCookieName")
.AddCookie("YourCookieName", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(30.0);
});
While I am not sure what the exact breaking change is for identityserver4, after cloning identityserver4 components and debugging I was able to isolate the the constructor for DefaultUserSession takes an IHttpContextAccessor that was arriving as null:
The constructor in question:
public DefaultUserSession(
IHttpContextAccessor httpContextAccessor,
IAuthenticationSchemeProvider schemes,
IAuthenticationHandlerProvider handlers,
IdentityServerOptions options,
ISystemClock clock,
ILogger<IUserSession> logger)
{ ...
The following solution gets you past the error, though hopefully identityserver4 will make this moot in a near future release.
You need to add an IHttpContextAccessor service in ConfigureServices:
public override void ConfigureServices(IServiceCollection services)
{
... other code omitted ...
services.AddScoped<IHttpContextAccessor>(provider => new
LocalHttpContextAccessor(provider));
... other code omitted ...
}
The LocalHttpContextAccessor is just a private class in the configuration class as seen here:
private class LocalHttpContextAccessor : IHttpContextAccessor
{
public IServiceProvider serviceProvider { get; private set; }
public HttpContext httpContext { get; set; }
public LocalHttpContextAccessor(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
this.httpContext = null;
}
public HttpContext HttpContext
{
get
{
return this.httpContext;
}
set
{
this.httpContext = null;
}
}
}
The problem is that at the point of configuring services, there is no current context to set, so I set it in a using block in the app builder configuration stage:
public override void Configure(IApplicationBuilder app,
IHostingEnvironment env)
{
app.Use(async (context, next) =>
{
IHttpContextAccessor httpContextAccessor;
httpContextAccessor = context.RequestServices.GetRequiredService<IHttpContextAccessor>();
if (httpContextAccessor is LocalHttpContextAccessor)
{
((LocalHttpContextAccessor)httpContextAccessor).httpContext = context;
}
await next();
});
... other code omitted ...
app.UseIdentityServer();
That will set the http context prior to running identity server code which fixes the bug. The scoped service should be created individually for each request. I've only recently made the full plunge into .net core from .net framework, so if there are scope or DI issues in that code that might lead to leaks or bad life-cycle, I'd appreciate the input. That said, at the very least that code keeps identity server 4 from crashing with the core 2.2+.

Categories