Q:

In an AspNetCore API controller, is there a well-accepted way to DRY out repetitious validation code?

The context: An AspNetCore controller I've been asked to maintain contains methods similar to the following:
    // Get api/Foo/ABCXXX/item/12345
    [HttpGet("{accountId}/item/{itemNumber}")]
    public async Task<ActionResult<ItemViewModel>> GetFoo([FromRoute] string accountId, [FromRoute] int itemNumber)
    {
        if (string.IsNullOrWhiteSpace(accountId))
        {
            return BadRequest("accountId must be provided");
        }

        if (itemNumber < 0)
        {
            return BadRequest("itemNumber must be positive");
        }

        if (!await CanAccessAccountAsync(accountId))
        {
            return Forbid();
        }

        // Returns null if account or item not found
        var result = _fooService.GetItem(accountId, itemNumber);

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

        return result;
    }

    // GET api/Foo/ABCXXX
    [HttpGet("{accountId}")]
    public async Task<ActionResult<IEnumerable<ItemViewModel>>> GetFoos([FromRoute] string accountId)
    {
        if (string.IsNullOrWhiteSpace(accountId))
        {
            return BadRequest("accountId must be provided");
        }

        if (!await CanAccessAccountAsync(accountId))
        {
            return Forbid();
        }

        // Returns null if account not found
        var results = _fooService.GetItems(accountId);

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

        return Ok(results);
    }

You may assume that there are more than 2 such methods with very similar parts.
Looking at this code makes me itchy—there appears to be a lot of repetition, but the repeated parts can't be extracted to their own methods because they contain return statements.
To me, it would make sense for these early exits to be exceptions rather than return values. Say, for the sake of argument, that I define an exception to wrap an IActionResult:
internal class ActionResultException : Exception
{
    public ActionResultException(IActionResult actionResult)
    {
        ActionResult = actionResult;
    }

    public IActionResult ActionResult { get; }
}

Then I can extract some specific validations:
    private void CheckAccountId(string accountId)
    {
        if (string.IsNullOrWhiteSpace(accountId))
        {
            throw new ActionResultException(BadRequest("accountId must be provided"));
        }
    }

    private async Task CheckAccountIdAccessAsync(string accountId)
    {
        if (!await CanAccessAccountAsync(accountId))
        {
            throw new ActionResultException(Forbid());
        }
    }

    private void CheckItemNumber(int itemNumber)
    {
        if (itemNumber < 0)
        {
            throw new ActionResultException(BadRequest("itemNumber must be positive"));
        }
    }

And rewrite the controller to use them:
    // Get api/Foo/ABCXXX/item/12345
    [HttpGet("{accountId}/item/{itemNumber}")]
    public async Task<IActionResult> GetFoo([FromRoute] string accountId, [FromRoute] int itemNumber)
    {
        try
        {
            CheckAccountId(accountId);
            CheckItemNumber(itemNumber);
            await CheckAccountIdAccessAsync(accountId);

            // Returns null if account or item not found
            var result = _fooService.GetItem(accountId, itemNumber);

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

            return Ok(result);
        }
        catch (ActionResultException e)
        {
            return e.ActionResult;
        }
    }

    // GET api/Foo/ABCXXX
    [HttpGet("{accountId}")]
    public async Task<IActionResult> GetFoos([FromRoute] string accountId)
    {
        try
        {
            CheckAccountId(accountId);
            await CheckAccountIdAccessAsync(accountId);

            // Returns null if account not found
            var results = _fooService.GetItems(accountId);

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

            return Ok(results);
        }
        catch (ActionResultException e)
        {
            return e.ActionResult;
        }
    }

To get this to work, I had to wrap the controller bodies in a try to unwrap the action result from the exception.
I also had to revert the return types to IActionResult—there are reasons I may prefer not to do that. The only thing I can think of to address that problem is to go more specific with the exceptions and catches, but this seems only to shift the WET-ness from the validation code to the catch blocks.
// Exceptions
internal class AccessDeniedException : Exception { ... }
internal class BadParameterException : Exception { ... }

    // Controller

    private void CheckAccountId(string accountId)
    {
        if (string.IsNullOrWhiteSpace(accountId))
        {
            throw new BadParameterException("accountId must be provided");
        }
    }

    private async Task CheckAccountIdAccessAsync(string accountId)
    {
        if (!await CanAccessAccountAsync(accountId))
        {
            throw new AccessDeniedException();
        }
    }

    private void CheckItemNumber(int itemNumber)
    {
        if (itemNumber < 0)
        {
            throw new BadParameterException("itemNumber must be positive");
        }
    }

    // Get api/Foo/ABCXXX/item/12345
    [HttpGet("{accountId}/item/{itemNumber}")]
    public async Task<IActionResult> GetFoo([FromRoute] string accountId, [FromRoute] int itemNumber)
    {
        try
        {
            ...
        }
        catch (AccessDeniedException)
        {
            return Forbid();
        }
        catch(BadParameterException e)
        {
            return BadRequest(e.Message);
        }
    }

    // GET api/Foo/ABCXXX
    [HttpGet("{accountId}")]
    public async Task<IActionResult> GetFoos([FromRoute] string accountId)
    {
        try
        {
            ...
        }
        catch (AccessDeniedException)
        {
            return Forbid();
        }
        catch (BadParameterException e)
        {
            return BadRequest(e.Message);
        }
    }

A:

There's a few simple things you can do. No need to go overboard at this point.
First and foremost, checking whether accountId is null or whitespace is completely superfluous. It's part of the route; if something isn't stuck in there, you wouldn't get here in the first place.
Second, you can make judicious use of route constraints where appropriate. For example, for your itemNumber being positive:
[HttpGet("{accountId}/item/{itemNumber:min(0)}")]

Though, honestly, I'm not sure something like /XXXX/item/-1 would even work in the first place. Regardless, specifying a min value will cover you.
Third, your CanAccessAccount check should actually be handled via resource-based authorization, built-in to ASP.NET Core. 
Long and short, if you use what's already available to you, you actually don't need to do much additional validation, in the first place, negating the need to find some way to "factor it out".

