Thursday, August 20, 2020

Custom Owin Middleware in Asp.Net PipeLine

Before starting the fundamental about Owin Middleware, Let me start with Owin (Open web interface) in .net.

Owin come in picture when Asp.net decoupled web app and web servers.It defines a standard way for middleware to be used in a pipeline to handle requests and associated responses .

Owin Middleware :- This is a component that could be use to control request and response in Asp.net.

A good takeaway from the above is that the Next property is optional, meaning we can stop the pipeline ourselves by simply not invoking the next OwinMiddleware.

To implement OwinMiddleware we will need a constructor that calls the base constructor and an implementation of the Invoke method. Let's spin up an example.

  Required Packages

Install-Package Microsoft.Owin.Host.SystemWeb

The abstract OwinMiddleware class looks like this:

/// <summary>
/// An abstract base class for a standard middleware pattern.
/// </summary>
public abstract class OwinMiddleware {

    /// <summary>
    /// Instantiates the middleware with an optional pointer to the next component.
    /// </summary>
    /// <param name="next"></param>
    protected OwinMiddleware(OwinMiddleware next) {
        Next = next;
    }

    /// <summary>
    /// The optional next component.
    /// </summary>
    protected OwinMiddleware Next { get; set; }

    /// <summary>
    /// Process an individual request.
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public abstract Task Invoke(IOwinContext context);
}

This is then registered like in StartUp class

app.Use(typeof(MyMiddlewareClass));

Here is the example of Custom Health Check Owin middleware in Asp.net :- 

We will take two 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());

            }

        }
    } 


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);
        }
    }

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))
                }
            });


No comments:

Post a Comment

Kafka setup in window

Here's a step-by-step guide on how to do it : 1. Prerequisites : Before you begin, make sure you have the following prerequisites inst...