In my previous article, I have explain Asp.net owin middleware in detail.
Here is the example of Custom Health Check Owin middleware in Asp.net :-
We will take three different type of health check middleware pipeline example
public static class AppBuilderExtensions
{
public static IAppBuilder UseHealthCheck(this IAppBuilder app, string route, HealthCheckConfiguration config = null)
{
config = config ?? new HealthCheckConfiguration();
return app.Map(route, x => x.Use<HealthCheckMiddleware>(config));
}
}
public class HealthCheckConfiguration
{
public TimeSpan Timeout { get; set; }
public IList<ICheckHealth> HealthChecks { get; set; }
public HealthCheckConfiguration(TimeSpan timeout = default(TimeSpan), IList<ICheckHealth> healthchecks = null)
{
Timeout = timeout;
HealthChecks = healthchecks;
}
}
public interface ICheckHealth
{
string Name { get; }
Task<HealthCheckStatus> Check();
}
public sealed class HealthCheckStatus
{
public string Message { get; set; }
public bool HasFailed { get; set; }
public HealthCheckStatus(string message, bool hasFailed)
{
Message = message;
HasFailed = hasFailed;
}
public static HealthCheckStatus Failed(string message = null)
{
return new HealthCheckStatus(message ?? "Failed", true);
}
public static HealthCheckStatus Passed(string message = null)
{
return new HealthCheckStatus(message ?? "Success", false);
}
}
public class HealthCheckMiddleware : OwinMiddleware
{
private IList<ICheckHealth> _healthChecks;
private readonly TimeSpan _timeout;
public HealthCheckMiddleware(OwinMiddleware next, HealthCheckConfiguration config) : base(next)
{
_healthChecks = (config.HealthChecks ?? Enumerable.Empty<ICheckHealth>()).ToArray(); ;
_timeout = config.Timeout;
}
public override async Task Invoke(IOwinContext context)
{
if (!string.IsNullOrEmpty(context.Request.Path.Value))
{
await Next.Invoke(context);
return;
}
//var queryParameters = context.Request.GetQueryParameters();
//var debug = false;
//if (queryParameters.ContainsKey("debug"))
// bool.TryParse(queryParameters["debug"], out debug);
var checkTasks = _healthChecks.Select(async x =>
{
try
{
return new
{
Name = x.Name,
Status = await x.Check().ConfigureAwait(false)
};
}
catch (Exception e)
{
return new
{
Name = x.Name,
Status = HealthCheckStatus.Failed(e.Message)
};
}
});
var allTasks = Task.WhenAll(checkTasks.ToArray());
if (allTasks != await Task.WhenAny(allTasks, Task.Delay(_timeout)).ConfigureAwait(false))
{
context.Response.StatusCode = (int)HttpStatusCode.GatewayTimeout;
}
else
{
var results = allTasks.Result;
var hasFailed = results.Any(x => x.Status.HasFailed);
context.Response.StatusCode = hasFailed ? (int)HttpStatusCode.ServiceUnavailable : (int)HttpStatusCode.OK;
var sb = new StringBuilder();
foreach (var r in results)
sb.AppendLine(r.Name + ": " + r.Status.Message);
await context.Response.WriteAsync(sb.ToString());
}
}
}
public class ComponentHealth
{
public string ComponentName { get; set; }
public ComponentStatus StatusName
{
get { return Status; }
}
public ComponentStatus Status { get; set; }
public ComponentHealth(string componentName, ComponentStatus status)
{
ComponentName = componentName;
Status = status;
}
public abstract class BaseHealthCheck : ICheckHealth
{
public string Name { get; set; }
public HealthStatus CheckHealth()
{
return new HealthStatus()
{
Components = new[] { new ComponentHealth("Universe", ComponentStatus.Weird) }
};
}
public abstract Task<HealthCheckStatus> Check();
}
}
public enum ComponentStatus
{
Healthy, // Ok
Fatal, // Error
Weird // Might be problems
}
public class HealthStatus
{
public IEnumerable<ComponentHealth> Components { get; set; }
}
1. HttpHealthCheck :- This pipeline will use to check the http client health check in Asp.net pipeline
public class HttpHealthCheck : BaseHealthCheck
{
private readonly Uri _uri;
private readonly ICredentials _credentials;
public HttpHealthCheck(string name, Uri uri, ICredentials credentials = null)
{
Name = name;
_uri = uri;
_credentials = credentials;
}
public override async Task<HealthCheckStatus> Check()
{
var request = (HttpWebRequest)WebRequest.Create(_uri);
request.UserAgent = "Owin.HealthCheck";
request.Method = "GET";
request.Credentials = _credentials;
using (var response = (HttpWebResponse)await request.GetResponseAsync().ConfigureAwait(false))
{
var statusCode = (int)response.StatusCode;
if (statusCode >= 200 && statusCode < 300)
return HealthCheckStatus.Passed(response.StatusDescription);
else
return HealthCheckStatus.Failed(response.StatusDescription);
}
}
}
2. Ping Health Check Pipeline:- This pipeline is used to check the server health while process the request
public class PingHealthCheck : BaseHealthCheck
{
private readonly string _host;
private readonly TimeSpan _timeout;
public PingHealthCheck(string name, string host, TimeSpan timeout)
{
if (string.IsNullOrEmpty(host))
throw new ArgumentException(string.Format("host cannot be empty {0}", name));
Name = name;
_host = host;
_timeout = timeout;
}
public override async Task<HealthCheckStatus> Check()
{
var ping = new Ping();
var result = await ping.SendPingAsync(_host, (int)_timeout.TotalMilliseconds).ConfigureAwait(false);
return new HealthCheckStatus(result.Status.ToString(), result.Status != IPStatus.Success);
}
}
3. Sql Health Check :- This pipeline will use to check the sql db health check in Asp.net framework
public class SqlHealthCheck : BaseHealthCheck
{
private readonly string _connectionString;
public SqlHealthCheck(string name, string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
throw new ArgumentException(string.Format("invalid connection string {0}",connectionString));
Name = name;
_connectionString = connectionString;
}
public override async Task<HealthCheckStatus> Check()
{
using (var connection = new SqlConnection(_connectionString))
{
await connection.OpenAsync().ConfigureAwait(false);
var cmd = new SqlCommand("select 1", connection);
await cmd.ExecuteScalarAsync().ConfigureAwait(false);
return HealthCheckStatus.Passed();
}
}
}
To register the middleware you need to pass the type
app.UseHealthCheck("/healthcheck", new HealthCheckConfiguration
{
Timeout = TimeSpan.FromSeconds(20),
HealthChecks = new List<ICheckHealth>()
{
new HttpHealthCheck("Google Check", new Uri("https://www.google.com")),
new PingHealthCheck("Local Ping", "localhost", TimeSpan.FromSeconds(10))
}
});