/// <summary>
        /// Processes the specified block action.
        /// </summary>
        /// <param name="controller">The API controller that initiated this action.</param>
        /// <param name="block">The block.</param>
        /// <param name="actionName">Name of the action.</param>
        /// <param name="actionParameters">The action parameters.</param>
        /// <param name="bodyParameters">The posted body parameters.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException">actionName
        /// or
        /// actionData</exception>
        internal static IHttpActionResult InvokeAction(ApiControllerBase controller, Blocks.IRockBlockType block, string actionName, Dictionary <string, JToken> actionParameters, JToken bodyParameters)
        {
            // Parse the body content into our normal parameters.
            if (bodyParameters != null)
            {
                try
                {
                    // Parse any posted parameter data, existing query string
                    // parameters take precedence.
                    foreach (var kvp in bodyParameters.ToObject <Dictionary <string, JToken> >())
                    {
                        actionParameters.AddOrIgnore(kvp.Key, kvp.Value);
                    }
                }
                catch
                {
                    return(new BadRequestErrorMessageResult("Invalid parameter data.", controller));
                }
            }

            //
            // Find the action they requested. First search by name
            // and then further filter by any method constraint attributes.
            //
            var actions = block.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public)
                          .Where(m => m.GetCustomAttribute <Blocks.BlockActionAttribute>()?.ActionName == actionName)
                          .ToList();

            if (actions.Count == 0)
            {
                return(new NotFoundResult(controller));
            }

            var action = FindBestActionForParameters(actions, actionParameters);

            if (action == null)
            {
                // This is an actual configuration error, so throw an error.
                throw new AmbiguousMatchException("The request matched multiple actions.");
            }

            var methodParameters = action.GetParameters();
            var parameters       = new List <object>();

            //
            // Go through each parameter and convert it to the proper type.
            //
            for (int i = 0; i < methodParameters.Length; i++)
            {
                // Check if this parameter is requesting it's content from the body.
                if (methodParameters[i].GetCustomAttribute <FromBodyAttribute>() != null)
                {
                    if (bodyParameters != null)
                    {
                        parameters.Add(bodyParameters.ToObject(methodParameters[i].ParameterType));
                    }
                    else if (methodParameters[i].IsOptional)
                    {
                        parameters.Add(Type.Missing);
                    }
                    else
                    {
                        return(new BadRequestErrorMessageResult($"Parameter '{methodParameters[i].Name}' is required.", controller));
                    }

                    continue;
                }

                var key = actionParameters.Keys.SingleOrDefault(k => k.ToLowerInvariant() == methodParameters[i].Name.ToLower());

                if (key != null)
                {
                    try
                    {
                        //
                        // If the target type is nullable and the action parameter is an empty
                        // string then consider it null. A GET query cannot have null values.
                        //
                        if (Nullable.GetUnderlyingType(methodParameters[i].ParameterType) != null)
                        {
                            if (actionParameters[key].Type == JTokenType.String && actionParameters[key].ToString() == string.Empty)
                            {
                                parameters.Add(null);

                                continue;
                            }
                        }

                        parameters.Add(actionParameters[key].ToObject(methodParameters[i].ParameterType));
                    }
                    catch (Exception ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex.Message);

                        return(new BadRequestErrorMessageResult($"Parameter type mismatch for '{methodParameters[i].Name}'.", controller));
                    }
                }
                else if (methodParameters[i].IsOptional)
                {
                    parameters.Add(Type.Missing);
                }
                else
                {
                    return(new BadRequestErrorMessageResult($"Parameter '{methodParameters[i].Name}' is required.", controller));
                }
            }

            object result;

            try
            {
                result = action.Invoke(block, parameters.ToArray());
            }
            catch (TargetInvocationException ex)
            {
                ExceptionLogService.LogApiException(ex.InnerException, controller.Request, GetPerson(controller, null)?.PrimaryAlias);
                result = new BlockActionResult(HttpStatusCode.InternalServerError, GetMessageForClient(ex));
            }
            catch (Exception ex)
            {
                ExceptionLogService.LogApiException(ex, controller.Request, GetPerson(controller, null)?.PrimaryAlias);
                result = new BlockActionResult(HttpStatusCode.InternalServerError, GetMessageForClient(ex));
            }

            //
            // Handle the result type.
            //
            if (result is IHttpActionResult httpActionResult)
            {
                return(httpActionResult);
            }
            else if (result is BlockActionResult actionResult)
            {
                var isErrorStatusCode = ( int )actionResult.StatusCode >= 400;

                if (isErrorStatusCode && actionResult.Content is string)
                {
                    return(new NegotiatedContentResult <HttpError>(actionResult.StatusCode, new HttpError(actionResult.Content.ToString()), controller));
                }
                else if (actionResult.Error != null)
                {
                    return(new NegotiatedContentResult <HttpError>(actionResult.StatusCode, new HttpError(actionResult.Error), controller));
                }
                else if (actionResult.Content is HttpContent httpContent)
                {
                    var response = controller.Request.CreateResponse(actionResult.StatusCode);
                    response.Content = httpContent;
                    return(new ResponseMessageResult(response));
                }
                else if (actionResult.ContentClrType != null)
                {
                    var genericType = typeof(System.Web.Http.Results.NegotiatedContentResult <>).MakeGenericType(actionResult.ContentClrType);
                    return(( IHttpActionResult )Activator.CreateInstance(genericType, actionResult.StatusCode, actionResult.Content, controller));
                }
                else
                {
                    return(new StatusCodeResult(actionResult.StatusCode, controller));
                }
            }
            else if (action.ReturnType == typeof(void))
            {
                return(new OkResult(controller));
            }
            else
            {
                return(new OkNegotiatedContentResult <object>(result, controller));
            }
        }
        /// <summary>
        /// Processes the specified block action.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <param name="verb">The HTTP Method Verb that was used for the request.</param>
        /// <param name="actionName">Name of the action.</param>
        /// <param name="actionParameters">The action parameters.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException">
        /// actionName
        /// or
        /// actionData
        /// </exception>
        private IHttpActionResult InvokeAction(Blocks.IRockBlockType block, string verb, string actionName, Dictionary <string, JToken> actionParameters)
        {
            MethodInfo action;

            //
            // Find the action they requested.
            //
            action = block.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public)
                     .SingleOrDefault(m => m.GetCustomAttribute <Blocks.BlockActionAttribute>()?.ActionName == actionName);

            if (action == null)
            {
                return(NotFound());
            }

            var methodParameters = action.GetParameters();
            var parameters       = new List <object>();

            //
            // Go through each parameter and convert it to the proper type.
            //
            for (int i = 0; i < methodParameters.Length; i++)
            {
                var key = actionParameters.Keys.SingleOrDefault(k => k.ToLowerInvariant() == methodParameters[i].Name.ToLower());

                if (key != null)
                {
                    try
                    {
                        //
                        // If the target type is nullable and the action parameter is an empty
                        // string then consider it null. A GET query cannot have null values.
                        //
                        if (Nullable.GetUnderlyingType(methodParameters[i].ParameterType) != null)
                        {
                            if (actionParameters[key].Type == JTokenType.String && actionParameters[key].ToString() == string.Empty)
                            {
                                parameters.Add(null);

                                continue;
                            }
                        }

                        parameters.Add(actionParameters[key].ToObject(methodParameters[i].ParameterType));
                    }
                    catch
                    {
                        return(BadRequest($"Parameter type mismatch for '{methodParameters[i].Name}'."));
                    }
                }
                else if (methodParameters[i].IsOptional)
                {
                    parameters.Add(Type.Missing);
                }
                else
                {
                    return(BadRequest($"Parameter '{methodParameters[i].Name}' is required."));
                }
            }

            object result;

            try
            {
                result = action.Invoke(block, parameters.ToArray());
            }
            catch (TargetInvocationException ex)
            {
                ExceptionLogService.LogApiException(ex.InnerException, Request, GetPersonAlias());
                result = new Rock.Blocks.BlockActionResult(HttpStatusCode.InternalServerError);
            }
            catch (Exception ex)
            {
                ExceptionLogService.LogApiException(ex, Request, GetPersonAlias());
                result = new Rock.Blocks.BlockActionResult(HttpStatusCode.InternalServerError);
            }

            //
            // Handle the result type.
            //
            if (result is IHttpActionResult)
            {
                return(( IHttpActionResult )result);
            }
            else if (result is Rock.Blocks.BlockActionResult actionResult)
            {
                if (actionResult.Error != null)
                {
                    return(Content(actionResult.StatusCode, new HttpError(actionResult.Error)));
                }
                else if (actionResult.Content is HttpContent httpContent)
                {
                    var response = Request.CreateResponse(actionResult.StatusCode);
                    response.Content = httpContent;
                    return(new System.Web.Http.Results.ResponseMessageResult(response));
                }
                else if (actionResult.ContentClrType != null)
                {
                    var genericType = typeof(System.Web.Http.Results.NegotiatedContentResult <>).MakeGenericType(actionResult.ContentClrType);
                    return(( IHttpActionResult )Activator.CreateInstance(genericType, actionResult.StatusCode, actionResult.Content, this));
                }
                else
                {
                    return(StatusCode(actionResult.StatusCode));
                }
            }
            else if (action.ReturnType == typeof(void))
            {
                return(Ok());
            }
            else
            {
                return(Ok(result));
            }
        }