/// <summary>
        /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler" /> interface.
        /// </summary>
        /// <param name="context">An <see cref="T:System.Web.HttpContext" /> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
        public void ProcessRequest(HttpContext context)
        {
            ApiEventLog eventLog = null;
            var entryStamp = DateTime.UtcNow;
            RestApiSettings settings = DefaultSettings;
            RuntimeContext runtimeContext = null;
            string traceId = null;
            BaseException baseException = null;

            context.Response.Headers.Add(HttpConstants.HttpHeader.SERVERENTRYTIME, entryStamp.ToFullDateTimeTzString());

            var acceptEncoding = context.Request.Headers[HttpConstants.HttpHeader.AcceptEncoding].SafeToLower();

            try
            {
                traceId = context.Request.TryGetHeader(HttpConstants.HttpHeader.TRACEID);

                Prepare(context.Request);

                if (context.Request.HttpMethod.Equals(HttpConstants.HttpMethod.Options, StringComparison.OrdinalIgnoreCase))
                {
                    if (this.AllowOptions)
                    {
                        //Return directly. IIS would append following headers by default, according to what exactly web.config have.
                        //Access-Control-Allow-Origin: *
                        //Access-Control-Allow-Headers: Content-Type
                        //Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
                        return;
                    }
                }

                runtimeContext = ProcessRoute(context.Request);
                runtimeContext.CheckNullObject("runtimeContext");

                settings = runtimeContext.Settings ?? DefaultSettings;

                if (runtimeContext.Version.Equals(BuildInFeatureVersionKeyword, StringComparison.OrdinalIgnoreCase))
                {
                    var buildInResult = ProcessBuildInFeature(runtimeContext, context.Request.IsLocal);
                    PackageOutput(context.Response, buildInResult, null, acceptEncoding, runtimeContext.IsVoid ?? false, settings);
                }
                else
                {
                    //Initialize additional header keys
                    if (runtimeContext.CustomizedHeaderKeys.HasItem())
                    {
                        var currentApiContext = ContextHelper.ApiContext;

                        foreach (var one in runtimeContext.CustomizedHeaderKeys)
                        {
                            currentApiContext.CustomizedHeaders.Merge(one, context.Request.TryGetHeader(one));
                        }
                    }

                    if (settings != null && settings.ApiTracking != null && settings.TrackingEvent)
                    {
                        eventLog = new ApiEventLog
                        {
                            RawUrl = context.Request.RawUrl,
                            EntryStamp = entryStamp,
                            UserAgent = context.Request.UserAgent,
                            TraceId = traceId,
                            // If request came from ApiTransport or other proxy ways, ORIGINAL stands for the IP ADDRESS from original requester.
                            IpAddress = context.Request.TryGetHeader(HttpConstants.HttpHeader.ORIGINAL).SafeToString(context.Request.UserHostAddress),
                            CultureCode = context.Request.UserLanguages == null ? null : context.Request.UserLanguages.FirstOrDefault(),
                            ContentLength = context.Request.ContentLength
                        };
                    }

                    InitializeContext(context.Request, runtimeContext);

                    if (!string.IsNullOrWhiteSpace(traceId))
                    {
                        ApiTraceContext.Initialize(runtimeContext, traceId, entryStamp);
                    }

                    if (eventLog != null)
                    {
                        if (runtimeContext.ApiInstance != null)
                        {
                            eventLog.ServiceIdentifier = runtimeContext.ApiInstance.GetType().Name;
                        }

                        if (runtimeContext.ApiMethod != null)
                        {
                            eventLog.ApiFullName = runtimeContext.ApiMethod.Name;
                        }

                        eventLog.ResourceName = runtimeContext.ResourceName;
                        eventLog.ServerIdentifier = EnvironmentCore.ServerName;
                        eventLog.UserIdentifier = runtimeContext.UserIdentifier;

                        var clientIdentifierHeaderKey = settings.ClientIdentifierHeaderKey;
                        if (!string.IsNullOrWhiteSpace(clientIdentifierHeaderKey))
                        {
                            eventLog.ClientIdentifier = context.Request.TryGetHeader(clientIdentifierHeaderKey);
                        }

                        eventLog.ModuleName = runtimeContext.ModuleName;
                        eventLog.ResourceEntityKey = runtimeContext.IsActionUsed ? runtimeContext.Parameter2 : runtimeContext.Parameter1;
                    }

                    if (runtimeContext.Exception != null)
                    {
                        throw runtimeContext.Exception;
                    }

                    try
                    {
                        if (runtimeContext.ApiTransportAttribute != null)
                        {
                            if (runtimeContext.ApiTransportAttribute.ApiTransportAdapter != null)
                            {
                                runtimeContext.ApiTransportAttribute.ApiTransportAdapter.PrepareInteraction(runtimeContext.ApiMethod, runtimeContext.ApiInstance, runtimeContext.EntityKey);
                            }

                            InvokeByTransport(context.Request, context.Response, runtimeContext.ApiTransportAttribute);
                        }
                        else
                        {
                            string jsonBody;
                            var invokeResult = Invoke(runtimeContext.ApiInstance, runtimeContext.ApiMethod, context.Request, runtimeContext.EntityKey, out jsonBody);

                            if (eventLog != null && !string.IsNullOrWhiteSpace(jsonBody))
                            {
                                eventLog.Content = jsonBody.Length > 50 ? ((jsonBody.Substring(0, 40) + "..." + jsonBody.Substring(jsonBody.Length - 6, 6))) : jsonBody;
                            }

                            PackageOutput(context.Response, invokeResult, null, acceptEncoding, runtimeContext.IsVoid ?? false, settings);
                        }
                    }
                    catch (Exception invokeEx)
                    {
                        throw invokeEx.Handle("Invoke", new { Url = context.Request.RawUrl, Method = context.Request.HttpMethod });
                    }
                }
            }
            catch (Exception ex)
            {
                var apiTracking = settings?.ApiTracking;
                baseException = ex.Handle("ProcessRequest", new { Uri = context.Request.Url.ToString() });

                (apiTracking ?? Framework.ApiTracking).LogException(baseException.ToExceptionInfo(runtimeContext?.ApiServiceName, EnvironmentCore.ServerName));

                if (eventLog != null)
                {
                    eventLog.ExceptionKey = baseException.Key;
                }

                PackageOutput(context.Response, null, baseException, acceptEncoding, settings: settings);
            }
            finally
            {

                if (settings?.ApiTracking != null)
                {
                    var exitStamp = DateTime.UtcNow;
                    if (eventLog != null)
                    {
                        try
                        {
                            eventLog.ExitStamp = exitStamp;
                            settings.ApiTracking.LogApiEvent(eventLog);
                        }
                        catch { }
                    }

                    if (ApiTraceContext.Root != null)
                    {
                        try
                        {
                            ApiTraceContext.Exit(baseException, exitStamp);

                            settings.ApiTracking.LogApiTraceLog(ApiTraceContext.GetCurrentTraceLog(true));
                        }
                        catch { }
                    }
                }

                ThreadExtension.Clear();
            }
        }
        /// <summary>
        /// Called by the ASP.NET MVC framework before the action method executes.
        /// </summary>
        /// <param name="filterContext">The filter context.</param>
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);

            DateTime entryStamp = DateTime.UtcNow;
            var request = filterContext.HttpContext.Request;

            var traceId = request.TryGetHeader(HttpConstants.HttpHeader.TRACEID);
            var traceSequence = request.TryGetHeader(HttpConstants.HttpHeader.TRACESEQUENCE).ObjectToNullableInt32();
            var methodInfo = (filterContext.ActionDescriptor as ReflectedActionDescriptor)?.MethodInfo;

            ContextHelper.ConsistContext(request, this.settings?.Name);

            if (!string.IsNullOrWhiteSpace(traceId))
            {
                ApiTraceContext.Initialize(traceId, traceSequence, entryStamp);
            }

            if (settings != null && settings.TrackingEvent)
            {
                var context = filterContext.HttpContext;
                ApiEvent = new ApiEventLog
                {
                    ApiFullName = methodInfo?.GetFullName(),
                    RawUrl = context.Request.RawUrl,
                    EntryStamp = entryStamp,
                    UserAgent = context.Request.UserAgent,
                    TraceId = traceId,
                    // If request came from ApiTransport or other proxy ways, ORIGINAL stands for the IP ADDRESS from original requester.
                    IpAddress = context.Request.TryGetHeader(settings?.OriginalIpAddressHeaderKey.SafeToString(HttpConstants.HttpHeader.ORIGINAL)).SafeToString(context.Request.UserHostAddress),
                    CultureCode = ContextHelper.ApiContext.CultureCode,
                    ContentLength = context.Request.ContentLength,
                    OperatorCredential = ContextHelper.CurrentCredential as BaseCredential,
                    Protocol = context.Request.Url.Scheme,
                    ReferrerUrl = context.Request.UrlReferrer?.ToString(),
                    ServerIdentifier = EnvironmentCore.ServerName,
                    ServiceIdentifier = EnvironmentCore.ProjectName
                };
            }

            var controllerType = methodInfo?.DeclaringType;

            var tokenRequiredAttribute = methodInfo?.GetCustomAttribute<TokenRequiredAttribute>(true) ?? controllerType?.GetCustomAttribute<TokenRequiredAttribute>(true);
            var permissionAttributes = controllerType?.GetCustomAttributes<ApiPermissionAttribute>(true).ToDictionary();
            permissionAttributes.Merge(methodInfo?.GetCustomAttributes<ApiPermissionAttribute>(true).ToDictionary(), true);

            var tokenRequired = tokenRequiredAttribute != null && tokenRequiredAttribute.TokenRequired;

            if (tokenRequired)
            {
                if (!ContextHelper.IsUser)
                {
                    var baseException = (new UnauthorizedTokenException(ContextHelper.Token)).Handle(
                   filterContext.HttpContext.Request.ToExceptionScene(filterContext.RouteData?.GetControllerName()), data: new { filterContext.HttpContext.Request.RawUrl });

                    HandleUnauthorizedAction(filterContext, baseException);
                }
                else if (permissionAttributes.HasItem())
                {
                    var baseException = ContextHelper.CurrentUserInfo?.Permissions.ValidateApiPermission(permissionAttributes, ContextHelper.Token, methodInfo?.GetFullName());

                    if (baseException != null)
                    {
                        HandleUnauthorizedAction(filterContext, baseException);
                    }
                }
            }

            ApiTraceContext.Enter(methodInfo.GetFullName(), setNameAsMajor: true);
        }