Implement HealthChecks for long running process .Net Core - c#

I have a long-running process: IHostedService. It runs all day long calling different external apis (services) to pull in data. I would like to get notified if during this process any of these external services failed/exceptions got thrown, the process is taking longer than 30 min etc. How would I set it up?
After some research, I ended up with this:
https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-5.0#separate-readiness-and-liveness-probes (StartupHostedServiceHealthCheck section)
How do I implement .NET Core Health Checks on a Hosted Service?
This is what I have so far:
// Registered as Singleton
public interface IHostedServiceStatus
{
bool IsHostedServiceRunning { get; set; }
DateTime RunTime { get; }
string Message { get; }
void Setup(string name, DateTime runTime, string message = null);
}
public class HostedServiceStatus : IHostedServiceStatus
{
private string _message;
private string _name;
public string Message => $"HostedService {_name} started on: {RunTime} failed to complete. {_message}";
public bool IsHostedServiceRunning { get; set; }
public DateTime RunTime { get; private set; }
public void Setup(string name, DateTime runTime, string message = null)
{
_name = name;
RunTime = runTime;
IsHostedServiceRunning = true;
if (!string.IsNullOrEmpty(message))
_message = message;
}
}
// HealthCheck
public class HostedServiceHealthCheck : IHealthCheck
{
private readonly IHostedServiceStatus _hostedServiceStatus;
private readonly TimeSpan _duration = TimeSpan.FromMinutes(30);
public HostedServiceHealthCheck(IHostedServiceStatus hostedServiceStatus)
{
_hostedServiceStatus = hostedServiceStatus;
}
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
if (_hostedServiceStatus.IsHostedServiceRunning)
{
if (_hostedServiceStatus.RunTime.Subtract(DateTime.Now).Duration() >= _duration)
return Task.FromResult(
HealthCheckResult.Unhealthy(_hostedServiceStatus.Message));
}
return Task.FromResult(
HealthCheckResult.Healthy("Task is finished."));
}
}
// Long running process
public async void Process(object state)
{
// each service runs about 10 min
foreach (var externalService in externalServices)
{
try
{
_hostedServiceStatus.Setup(externalService.Name, DateTime.Now); // setup healthcheckStatus - injected
...
// calls externalService gets data and saves to db
_dataMinerStatus.IsHostedServiceRunning = false; // udpate Healthcheck - finished successfully
}
catch (Exception ex)
{
// set MInDateTime so that it becamse UnHealthy
_hostedServiceStatus.Setup(externalService.Name, DateTime.MinValue);
// HealthCheck injected
await _healthCheck.CheckHealthAsync(new HealthCheckContext()); // send notification? (webhook setup) - will this work?
}
}

Related

gRPC - Limit client requests (Throttle)

Is there a way to add a throttle in gRPC Unary calls? My goal is to limit each user to 10 requests per second.
In my researches I found that (maybe) it would be placed in a class that inherits Interceptor, like the following:
public class LimitClientRequestsInterceptor : Interceptor
{
public override UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
// Add code to limit 10 requests per second for each user.
return await base.UnaryServerHandler(request, context, continuation);
}
}
I found a way to solve my problem, since gRPC does not have a built-in method (in c#) for rate limits or throttling. However, I was able to do it as shown below.
My interceptor class:
public class LimitClientRequestsInterceptor : Interceptor
{
private readonly ThrottleGauge _gauge;
public override UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
var username = context.GetHttpContext().User.GetUserNameInIdentity();
if (ThrottlingAttribute.UserHasReachedMaxRateLimit(username))
{
throw new RpcException(new Status(
StatusCode.Cancelled,
Newtonsoft.Json.JsonConvert.SerializeObject(new
{ Code = 429, Detail = $"Throttle: {username} exceeded
{_gauge.MaxMessagesPerTimeSlice} messages in {_gauge.TimeSlice}" })));
}
return await base.UnaryServerHandler(request, context, continuation);
}
}
My Throttle class:
internal class ThrottlingAttribute
{
private static Dictionary<string, ThrottleGauge> _byUser;
private static TimeSpan _defaultThrottle_TimeSliceInMilliseconds = TimeSpan.FromMilliseconds(ServiceSettings.Instance.DefaultThrottle_TimeSliceInMilliseconds);
private static int _defaultThrottle_MaxMessagesPerTimeSlice = ServiceSettings.Instance.DefaultThrottle_MaxMessagesPerTimeSlice;
public static bool UserHasReachedMaxRateLimit(string username)
{
ThrottleGauge gauge;
if (!_byUser.TryGetValue(username, out gauge))
{
gauge = new ThrottleGauge(
_defaultThrottle_TimeSliceInMilliseconds,
_defaultThrottle_MaxMessagesPerTimeSlice);
_byUser[username] = gauge;
}
return gauge.WillExceedRate();
}
}
And my ThrottleGauge class:
internal class ThrottleGauge
{
private readonly object _locker = new object();
private Queue<DateTime> _Queue = new Queue<DateTime>();
public TimeSpan TimeSlice { get; private set; }
public int MaxMessagesPerTimeSlice { get; private set; }
public ThrottleGauge(TimeSpan timeSlice, int maxMessagesPerTimeSlice)
{
TimeSlice = timeSlice;
MaxMessagesPerTimeSlice = maxMessagesPerTimeSlice;
}
// returns true if sending a message now message exceeds limit rate
public bool WillExceedRate()
{
lock (_locker)
{
var now = DateTime.Now;
if (_Queue.Count < MaxMessagesPerTimeSlice)
{
_Queue.Enqueue(now);
return false;
}
DateTime oldest = _Queue.Peek();
if ((now - oldest).TotalMilliseconds < TimeSlice.TotalMilliseconds)
return true;
_Queue.Dequeue();
_Queue.Enqueue(now);
return false;
}
}
}

How to pass complex objects via SignalR in C#?

My working SignalR ASP.NET Core 5 Windows Service with a simple string payload (or even more value type parameters) does not work anymore when I change it to a (simple) complex object ("CommonMessage" in my case). From what I read, it should work out of the box. The "SendCommonMessage" method doesn't get called anymore and I am not getting any error. What am I missing? Is there no way of debugging/getting the error shown? (The ASP.NET Service gets called by a WPF Core 5 application.)
public class CommonMessageHub : Hub
{
public async Task SendCommonMessage(CommonMessage commonMessage)
{
await Clients.All.SendAsync("ReceiveCommonMessage", commonMessage);
}
}
public class CommonMessage
{
public int MessageType { get; set; }
public int Id { get; set; }
public string Text { get; set; }
}
The caller (WPF app) looks like so:
public class CommonMessageService
{
public CommonMessageService(HubConnection connection)
{
_connection = connection;
_connection.On<CommonMessage>("ReceiveCommonMessage", commonMessage => CommonMessageReceived?.Invoke(commonMessage));
}
private readonly HubConnection _connection;
public event Action<CommonMessage> CommonMessageReceived;
public async Task Connect()
{
await _connection.StartAsync();
}
public async Task SendCommonMessage(CommonMessage commonMessage)
{
await _connection.SendAsync("SendCommonMessage", commonMessage);
}
}

how do I cache an output in ASP.NET Core

I have a API controller,and the scenario is:
I need to consume third party datasource(let's say the third party is provided as a dll file for simplicity, and the dll contain Student model and StudentDataSource that contain a lot of method to retrieve student ), and calling the third party data source is costly and data only gets updated every 6 hours.
so somehow I need to cache the output, below is some action method from my api controller:
// api controller that contain action methods below
[HttpGet]
public JsonResult GetAllStudentRecords()
{
var dataSource = new StudentDataSource();
return Json(dataSource.GetAllStudents());
}
[HttpGet("{id}")]
public JsonResult GetStudent(int id)
{
var dataSource = new StudentDataSource();
return Json(dataSource.getStudent(id));
}
then how should I cache the result especially for the second action method, it is dumb to cache every student result with different id
My team is implementing a similar caching strategy on an API controller using a custom Action filter attribute to handle the caching logic. See here for more info on Action filters.
The Action filter's OnActionExecuting method runs prior to your controller method, so you can check whether the data you're looking for is already cached and return it directly from here, bypassing the call to your third party datasource when cached data exists. We also use this method to check the type of request and reset the cache on updates and deletes, but it sounds like you won't be modifying data.
The Action filter's OnActionExecuted method runs immediately AFTER your controller method logic, giving you an opportunity to cache the response object before returning it to the client.
The specifics of how you implement the actual caching are harder to provide an answer for, but Microsoft provides some options for in-memory caching in .NET Core (see MemoryCache.Default not available in .NET Core?)
I used the solution with the cache strategy through the controller API as #chris-brenberg pointed out, it turned out like this
on controller class
[ServerResponseCache(false)]
[HttpGet]
[Route("cache")]
public ActionResult GetCache(string? dateFormat) {
Logger.LogInformation("Getting current datetime");
return Ok(new { date = DateTime.Now.ToString() });
}
on ServerResponseCacheAttribute.cs
namespace Site.Api.Filters {
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Primitives;
using System.Globalization;
using System.Threading.Tasks;
public class ServerResponseCacheAttribute : TypeFilterAttribute {
public ServerResponseCacheAttribute(bool byUserContext = true) : base(typeof(ServerResponseCacheAttributeImplementation)) =>
Arguments = new object[] { new ServerResponseCacheProps { ByUserContext = byUserContext } };
public ServerResponseCacheAttribute(int secondsTimeout, bool byUserContext = true) : base(typeof(ServerResponseCacheAttributeImplementation)) =>
Arguments = new object[] { new ServerResponseCacheProps { SecondsTimeout = secondsTimeout, ByUserContext = byUserContext } };
public class ServerResponseCacheProps {
public int? SecondsTimeout { get; set; }
public bool ByUserContext { get; set; }
}
public class ServerResponseCacheConfig {
public bool Disabled { get; set; }
public int SecondsTimeout { get; set; } = 60;
public string[] HeadersOnCache { get; set; } = { "Accept-Language" };
}
private class ServerResponseCacheAttributeImplementation : IAsyncActionFilter {
private string _cacheKey = default;
readonly ILogger<ServerResponseCacheAttributeImplementation> _logger;
readonly IMemoryCache _memoryCache;
readonly ServerResponseCacheConfig _config;
readonly bool _byUserContext;
public ServerResponseCacheAttributeImplementation(ILogger<ServerResponseCacheAttributeImplementation> logger,
IMemoryCache memoryCache, ServerResponseCacheProps props) {
_logger = logger;
_memoryCache = memoryCache;
_byUserContext = props.ByUserContext;
_config = new ServerResponseCacheConfig {
SecondsTimeout = props.SecondsTimeout ?? 60,
HeadersOnCache = new[] { "Accept-Language" }
};
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
if (next == null) {
throw new ArgumentNullException(nameof(next));
}
if (_config.Disabled) {
await next();
return;
}
OnActionExecutingAsync(context);
if (context.Result == null) {
OnActionExecuted(await next());
}
}
void OnActionExecutingAsync(ActionExecutingContext context) {
SetCacheKey(context.HttpContext.Request);
// Not use a stored response to satisfy the request. Will regenerates the response for the client, and updates the stored response in its cache.
bool noCache = context.HttpContext.Request.Headers.CacheControl.Contains("no-cache");
if (noCache) {
return;
}
TryLoadResultFromCache(context);
}
void SetCacheKey(HttpRequest request) {
if (request == null) {
throw new ArgumentException(nameof(request));
}
if (!string.Equals(request.Method, "GET", StringComparison.InvariantCultureIgnoreCase)) {
return;
}
List<string> cacheKeys = new List<string>();
if (_byUserContext && request.HttpContext.User.Identity.IsAuthenticated) {
cacheKeys.Add($"{request.HttpContext.User.Identity.Name}");
}
string uri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path, request.QueryString);
cacheKeys.Add(uri);
foreach (string headerKey in _config.HeadersOnCache) {
StringValues headerValue;
if (request.Headers.TryGetValue(headerKey, out headerValue)) {
cacheKeys.Add($"{headerKey}:{headerValue}");
}
}
_cacheKey = string.Join('_', cacheKeys).ToLower();
}
void TryLoadResultFromCache(ActionExecutingContext context) {
ResultCache resultCache;
if (_cacheKey != null && _memoryCache.TryGetValue(_cacheKey, out resultCache)) {
_logger.LogInformation("ServerResponseCache: Response loaded from cache, cacheKey: {cacheKey}, expires at: {expiration}.", _cacheKey, resultCache.Expiration);
context.Result = resultCache.Result;
SetExpiresHeader(context.HttpContext.Response, resultCache.Expiration);
}
}
/// <summary>Add expires header (the time after which the response is considered stale).</summary>
void SetExpiresHeader(HttpResponse response, DateTimeOffset expiration) {
string expireHttpDate = expiration.UtcDateTime.ToString("ddd, dd MMM yyyy HH:mm:ss", CultureInfo.InvariantCulture);
response.Headers.Add("Expires", $"{expireHttpDate} GMT");
}
void OnActionExecuted(ActionExecutedContext context) {
if (_cacheKey == null) {
return;
}
if (context.Result != null) {
DateTimeOffset expiration = SetCache(context.Result);
SetExpiresHeader(context.HttpContext.Response, expiration);
} else {
RemoveCache();
}
}
DateTimeOffset SetCache(IActionResult result) {
DateTimeOffset absoluteExpiration = DateTimeOffset.Now.AddSeconds(_config.SecondsTimeout);
ResultCache resultCache = new ResultCache {
Result = result,
Expiration = absoluteExpiration
};
_memoryCache.Set(_cacheKey, resultCache, absoluteExpiration);
_logger.LogInformation("ServerResponseCache: Response set on cache, cacheKey: {cacheKey}, until: {expiration}.", _cacheKey, absoluteExpiration);
return absoluteExpiration;
}
void RemoveCache() {
_memoryCache.Remove(_cacheKey);
_logger.LogInformation("ServerResponseCache: Response removed from cache, cacheKey: {cacheKey}.", _cacheKey);
}
}
private class ResultCache {
public IActionResult Result { get; set; }
public DateTimeOffset Expiration { get; set; }
}
}}
I hope it helps someone, best regards

ASP.NET Core API, Mediatr send method raises exception unable to resolve service for type

I have a ASP.NET Core API, where I am trying to use FluentValidation with Mediatr.
Currently when the controller method is attempting to call Send on the mediatr instance it generates:
Exception thrown: 'System.InvalidOperationException' in
Microsoft.Extensions.DependencyInjection.dll: 'Unable to resolve
service for type 'GetApplicationQuery' while attempting to activate
'GetApplicationQueryValidator'.'
The query, validator and response class look like this:
public class GetApplicationQuery : IRequest<Response>
{
private string _name;
public GetApplicationQuery(string name)
{
_name = name;
}
public string Name { get { return _name; } }
}
public class GetApplicationQueryHandler : IRequestHandler<GetApplicationQuery, Response>
{
public GetApplicationQueryHandler() { }
public async Task<Response> Handle(GetApplicationQuery request, CancellationToken cancellationToken)
{
return new Response("yadda yadda");
}
}
public class GetApplicationQueryValidator : AbstractValidator<GetApplicationQuery>
{
public GetApplicationQueryValidator(GetApplicationQuery request)
{
RuleFor(m => m.Name).MinimumLength(30).WithMessage("Name must be greater than 30 characters, long");
}
}
public class Response
{
private readonly IList<string> _messages = new List<string>();
public IEnumerable<string> Errors { get; }
public object Result { get; }
public Response() => Errors = new ReadOnlyCollection<string>(_messages);
public Response(object result) : this() => Result = result;
public Response AddError(string message)
{
_messages.Add(message);
return this;
}
}
The configuration I have in the Startup class looks like this:
public void ConfigureServices(IServiceCollection services)
{
AddMediatr(services);
services.AddMvc().AddFluentValidation(fv =>
{
fv.RegisterValidatorsFromAssemblyContaining<Startup>();
fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
private static void AddMediatr(IServiceCollection services)
{
const string applicationAssemblyName = "ApplicationApi";
var assembly = AppDomain.CurrentDomain.Load(applicationAssemblyName);
AssemblyScanner
.FindValidatorsInAssembly(assembly)
.ForEach(result => services.AddScoped(result.InterfaceType, result.ValidatorType));
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidatorHandler<,>));
services.AddMediatR(assembly);
}
I am guessing I have the configuration wrong but I have been changing configuration several times with no success.
Any guidance would be much appreciated
GetApplicationQueryValidator is taking GetApplicationQuery as a constructor dependency but the collection doesn't know about it to be able to inject it.
Also not seeing how it is to be used in that validator. I would suggest removing GetApplicationQuery from the constructor since it doesn't look like it is needed.
public class GetApplicationQueryValidator : AbstractValidator<GetApplicationQuery> {
public GetApplicationQueryValidator() {
RuleFor(m => m.Name).MinimumLength(30).WithMessage("Name must be greater than 30 characters, long");
}
}

Application Insights with invalid instrumentation key - how to handle as no error is thrown

Is there a way to capture an error when when an invalid instrumentation key is used when tracing messages to Application Insights?
I'm programmatically specifying an instrumentation key like below but no exception is thrown. I'm trying to build a Logging WebApi that will return a success or failure dependent on whether the message was successfully logged to Application Insights?
TelemetryConfiguration config = TelemetryConfiguration.CreateDefault();
config.InstrumentationKey = "ABC";
client.TrackTrace("Test"),SeverityLevel.Information);
You should implement your own channel that implements ITelemetryChannel, and handle exceptions as you want.
Here's a naive example:
public class SynchronousTelemetryChannel : ITelemetryChannel
{
private const string ContentType = "application/x-json-stream";
private readonly List<ITelemetry> _items;
private object _lock = new object();
public bool? DeveloperMode { get; set; }
public string EndpointAddress { get; set; }
public SynchronousTelemetryChannel()
{
_items = new List<ITelemetry>();
EndpointAddress = "https://dc.services.visualstudio.com/v2/track";
}
public void Send(ITelemetry item)
{
lock (_lock)
{
_items.Add(item);
}
}
public void Flush()
{
lock (_lock)
{
try
{
byte[] data = JsonSerializer.Serialize(_items);
new Transmission(new Uri(EndpointAddress), data, ContentType, JsonSerializer.CompressionType).SendAsync().Wait();
_items.Clear();
}
catch (Exception e)
{
// Do whatever you want.
}
}
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
Then, initialize the configuration with your channel via code or configuration file:
TelemetryConfiguration.Active.TelemetryChannel = new SynchronousTelemetryChannel();

Categories