public async Task <SpCallResult> Invoke(SpCall spCall, InvokeOptions spInvokeParams, bool isSystem = false)
        {
            if (spCall == null)
            {
                throw new ArgumentNullException(nameof(spCall));
            }

            // Check duplicate request
            var spInfo = await FindSpInfo($"{spCall.Method}");

            if (spInfo != null && spInfo.ExtendedProps.DataAccessMode == SpDataAccessMode.Write)
            {
                await CheckDuplicateRequest(spInvokeParams.ApiInvokeOptions.RequestId, spInfo.ExtendedProps.CommandTimeout);
            }

            // Call main invoke
            var spi = new InvokeOptionsInternal {
                SpInvokeParams = spInvokeParams, IsSystem = isSystem
            };

            if (isSystem)
            {
                spi.SpInvokeParams.AuthUserId = AppAuthUserId;
            }

            return(await Invoke(spCall, spi));
        }
        public async Task <SpCallResult> Invoke(string method, object param, InvokeOptions spInvokeParams, bool isSystem = false)
        {
            // Create spCall
            var spCall = new SpCall
            {
                Method = method
            };

            foreach (var propInfo in param.GetType().GetProperties())
            {
                spCall.Params.Add(propInfo.Name, propInfo.GetValue(param));
            }

            return(await Invoke(spCall, spInvokeParams, isSystem));
        }
        private async Task <SpCallResult> Invoke(SpCall spCall, InvokeOptionsInternal spi)
        {
            try
            {
                return(await InvokeCore(spCall, spi));
            }
            catch (SpInvokerAppVersionException)
            {
                await RefreshApi();

                return(await Invoke(spCall, spi));
            }
            catch (DirectSpException spException) //catch any read-only errors
            {
                throw spException.SpCallError.ErrorId == 3906 ? new SpMaintenanceReadOnlyException(spCall.Method) : spException;
            }
        }
        private async Task <SpCallResult> InvokeCore(SpCall spCall, InvokeOptionsInternal spi)
        {
            try
            {
                // Check captcha
                await CheckCaptcha(spCall, spi);

                // Call core
                var result = await InvokeCore2(spCall, spi);

                // Update result
                await UpdateRecodsetDownloadUri(spCall, spi, result);

                return(result);
            }
            catch (Exception ex)
            {
                throw DirectSpExceptionAdapter.Convert(ex, _captchaController);
            }
        }
        private async Task <SpCallResult> InvokeCore2(SpCall spCall, InvokeOptionsInternal spi)
        {
            //fix isLocal for system request and loopback
            if (spi.IsSystem)
            {
                spi.SpInvokeParams.IsLocalRequest = true;
            }

            // retrieve user session
            var invokeParams  = spi.SpInvokeParams;
            var invokeOptions = spi.SpInvokeParams.ApiInvokeOptions;
            var userSession   = _sessionManager.GetUserSession(invokeParams.AuthUserId, invokeParams.Audience);

            //Verify user request limit
            VerifyUserRequestLimit(userSession);

            //find api
            var spName = spCall.Method;

            if (spCall.Method == "System_api")
            {
                return(await Help());
            }

            var spInfo = await FindSpInfo(spName);

            if (spInfo == null)
            {
                var ex = new DirectSpException($"Could not find the API: {spName}");
                _logger?.LogWarning(ex.Message, ex);//Log exception
                throw ex;
            }

            //check IsCaptcha by meta-data
            if ((spInfo.ExtendedProps.CaptchaMode == SpCaptchaMode.Always || spInfo.ExtendedProps.CaptchaMode == SpCaptchaMode.Auto) && !spi.IsCaptcha)
            {
                throw new InvalidCaptchaException(await _captchaController.Create(), spInfo.ProcedureName);
            }

            //check IsBatchAllowed by meta-data
            if (!spInfo.ExtendedProps.IsBatchAllowed && spi.IsBatch)
            {
                throw new SpBatchIsNotAllowedException(spInfo.ProcedureName);
            }

            //Create DirectSpContext
            var context = new DirectSpContext()
            {
                IsBatch          = spi.IsBatch,
                IsCaptcha        = spi.IsCaptcha,
                RecordIndex      = invokeOptions.RecordIndex,
                RecordCount      = invokeOptions.RecordCount,
                AppVersion       = AppVersion,
                IsReadonlyIntent = spInfo.ExtendedProps.DataAccessMode == SpDataAccessMode.Read || spInfo.ExtendedProps.DataAccessMode == SpDataAccessMode.ReadSnapshot,
                RequestRemoteIp  = spi.SpInvokeParams.RequestRemoteIp?.ToString(),
                IsLocalRequest   = spi.SpInvokeParams.IsLocalRequest,
                UserAgent        = spi.SpInvokeParams.UserAgent,
                AppName          = AppName,
                Audience         = invokeParams.Audience,
                AuthUserId       = invokeParams.AuthUserId,
                ClientVersion    = null, //not implemented yet
                AgentContext     = userSession.AgentContext
            };

            //Get Connection String caring about ReadScale
            var isReadScale = IsReadScale(spInfo, userSession, spi, out bool isWriteMode);

            //create SqlParameters
            var spCallResults = new SpCallResult();
            var paramValues   = new Dictionary <string, object>();

            //set other caller params
            var spCallParams = spCall.Params ?? new Dictionary <string, object>();

            foreach (var spCallparam in spCallParams)
            {
                var paramName  = spCallparam.Key;
                var paramValue = spCallparam.Value;

                //find sqlParam for callerParam
                var spParam = spInfo.Params.FirstOrDefault(x => x.ParamName.Equals($"{paramName}", StringComparison.OrdinalIgnoreCase));
                if (spParam == null)
                {
                    throw new ArgumentException($"parameter '{paramName}' does not exists!");
                }
                spInfo.ExtendedProps.Params.TryGetValue(spParam.ParamName, out SpParamInfoEx spParamEx);

                //make sure Context has not been set by the caller
                if (paramName.Equals("Context", StringComparison.OrdinalIgnoreCase))
                {
                    throw new ArgumentException($"You can not set '{paramName}' parameter!");
                }

                // extract data from actual signed data
                var value = CheckJwt(paramValue, spParam, spParamEx);

                // convert data for db
                paramValue = ConvertDataForResource(value, spParam, spParamEx, invokeOptions);

                // add parameter
                paramValues.Add(paramName, paramValue);
            }

            // set all output value which have not been set yet
            foreach (var spParam in spInfo.Params)
            {
                if (spParam.IsOutput)
                {
                    if (string.Equals(spParam.ParamName, "Recordset", StringComparison.OrdinalIgnoreCase))
                    {
                        throw new DirectSpException($"{spInfo.ProcedureName} contains {spParam.ParamName} as a output parameter which is not supported!");
                    }

                    if (!paramValues.ContainsKey(spParam.ParamName))
                    {
                        paramValues.Add(spParam.ParamName, Undefined.Value);
                    }
                }
            }

            // execute the command
            var commandResult = await _commandProvider.Execute(spInfo, context, paramValues, isReadScale);

            // store agentContext
            userSession.AgentContext = commandResult.AgentContext;

            // fill Recordset and close dataReader BEFORE reading sqlParameters
            if (commandResult.Table != null)
            {
                ReadRecordset(spCallResults, commandResult.Table, spInfo, invokeOptions);
            }

            // build return params
            foreach (var outParam in commandResult.OutParams)
            {
                var outParamName  = outParam.Key;
                var outParamValue = outParam.Value;
                var spParam       = spInfo.Params.FirstOrDefault(x => x.ParamName.Equals($"{outParamName}", StringComparison.OrdinalIgnoreCase));
                spInfo.ExtendedProps.Params.TryGetValue(outParamName, out SpParamInfoEx spParamEx);

                // Sign text if is need
                if (spParamEx?.SignType == SpSignType.JwtByCertThumb)
                {
                    outParamValue = _tokenSigner.Sign(outParamValue.ToString());
                }

                // convert data form result
                var value = ConvertDataFromResource(outParamValue, invokeOptions);
                spCallResults.Add(outParamName, value);

                // add Alternative Calendar
                if (_alternativeCalendar.IsDateTime(spParam.SystemTypeName.ToString()))
                {
                    spCallResults.Add(_alternativeCalendar.GetFieldName(outParamName), _alternativeCalendar.FormatDateTime(value, spParam.SystemTypeName));
                }
            }

            userSession.SetWriteMode(isWriteMode);
            return(spCallResults);
        }
        public async Task <SpCallResult> Invoke(SpCall spCall)
        {
            var spInvokeParams = new InvokeOptions();

            return(await Invoke(spCall, spInvokeParams, true));
        }