private Type GetDeclaredReturnType(ControllerActionDescriptor action)
        {
            var declaredReturnType = action.MethodInfo.ReturnType;

            if (declaredReturnType == typeof(void) ||
                declaredReturnType == typeof(Task) ||
                declaredReturnType == typeof(ValueTask))
            {
                return(typeof(void));
            }

            // Unwrap the type if it's a Task<T>. The Task (non-generic) case was already handled.
            var unwrappedType = declaredReturnType;

            if (declaredReturnType.IsGenericType &&
                (declaredReturnType.GetGenericTypeDefinition() == typeof(Task <>) || declaredReturnType.GetGenericTypeDefinition() == typeof(ValueTask <>)))
            {
                unwrappedType = declaredReturnType.GetGenericArguments()[0];
            }

            // If the method is declared to return IActionResult or a derived class, that information
            // isn't valuable to the formatter.
            if (typeof(IActionResult).IsAssignableFrom(unwrappedType))
            {
                return(null);
            }

            // If we get here, the type should be a user-defined data type or an envelope type
            // like ActionResult<T>. The mapper service will unwrap envelopes.
            unwrappedType = _mapper.GetResultDataType(unwrappedType);
            return(unwrappedType);
        }