0

I have working code that uses the ExecuteAsync() method on a Polly policy (IAsyncPolicy<HttpResponseMessage>) to pull data from a web page. I'd like to add some debug logging to it, but so far I am not able to figure out the correct syntax to have this happen unless I wrap both the logging and http call in a separate method, and then call that via ExecuteAsync.

This doesn't seem "right" to me, and I feel there is a cleaner or more direct way. I think my issue is that I don't have a complete enough understanding of how Func<T> and lambda expressions truly operate to pull this off, as when I wrap things in {} I get errors like:

Not all code paths return a value in lambda expression of type Func<CancellationToken, Task<HttpResponseMessage>>.

This is my original working code:

var httpClient = _httpClientFactory.CreateClient();
HttpResponseMessage result;
var retryPolicy = (IAsyncPolicy<HttpResponseMessage>)_policyRegistry[RetryPolicyKeyName];

result = await retryPolicy.ExecuteAsync(
    async cancellationToken =>    
        await httpClient.GetAsync(myUrl, cancellationToken)
    , cancellationToken
);

This code produces the "Not all code paths return a value in lambda expression of type Func<CancellationToken, Task> error:

var httpClient = _httpClientFactory.CreateClient();
HttpResponseMessage result;
var retryPolicy = (IAsyncPolicy<HttpResponseMessage>)_policyRegistry[RetryPolicyKeyName];

result = await retryPolicy.ExecuteAsync(
    async cancellationToken =>    
    {
        await httpClient.GetAsync(myUrl, cancellationToken);
    }
    , cancellationToken
);

This is my "hack" that gets done what I want with an extra method:

private async Task<HttpResponseMessage> TestWithLogAsync(HttpClient httpClient, string myUrl, CancellationToken cancellationToken)
{
    _logger.LogDebug("Requesting from: {myUrl}", myUrl);
    var result = await httpClient.GetAsync(myUrl, cancellationToken);
    return result;
}

var httpClient = _httpClientFactory.CreateClient();
HttpResponseMessage result;
var retryPolicy = (IAsyncPolicy<HttpResponseMessage>)_policyRegistry[RetryPolicyKeyName];

result = await retryPolicy.ExecuteAsync(
    async cancellationToken =>
        await TestWithLogAsync(httpClient, myUrl, cancellationToken)
    , cancellationToken
);
  • Is this what you want? [Can a C# lambda expression have more than one statement?](https://stackoverflow.com/q/5653703/3744182). If not can you [edit] your question to share the code that generated the *`Not all code paths return a value in lambda expression of type 'Func`* error? – dbc Mar 14 '21 at 20:47
  • I have reviewed that link before posting this one, and I believe I am still missing something. Perhaps it is because I am passing a cancellationToken variable as an input to my lambda? This line was missing from my original submission, but I have added it now. Thanks for your comment. I have included the error producing code as suggested. – Ryan Kayser Mar 15 '21 at 21:56
  • @RyanKayser Have considered to use [Typed Client with resilient policies](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests)? With that approach a custom [DelegatingHandler](https://stackoverflow.com/questions/66248766/polly-log-all-requests-with-url-headers-content-and-response) can be injected to log everything. – Peter Csala Mar 16 '21 at 08:46

1 Answers1

1

What you have here:

result = await retryPolicy.ExecuteAsync(
    await httpClient.GetAsync(managementUrl, cancellationToken)
    , cancellationToken
);

Is equivalent to:

result = await retryPolicy.ExecuteAsync(async () => {
      return await httpClient.GetAsync(managementUrl, cancellationToken);
    }
    , cancellationToken
);

Hence this will execute more than one line in the lambda:

result = await retryPolicy.ExecuteAsync(async () => {
      _logger.LogDebug("Requesting from: {managementUrl}", managementUrl);
      return await httpClient.GetAsync(managementUrl, cancellationToken);
    }
    , cancellationToken
);

..though I wasn't quite clear on how managementUrl morphed into myUrl across your examples, and whether they differ.. I've assumed not

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
  • You are correct, managementUrl and myUrl do not differ. I've updated the code to be consistent. I also mis-pasted my original code and neglected to include the line: async cancellationToken => When I include the {} as you have suggested, I get this error with a red squiggly under the =>: "Not all code paths return a value in lambda expression of type 'Func'". – Ryan Kayser Mar 15 '21 at 21:48
  • Are you sure you copied the code as written? My suggestions definitely feature a `return` - did you miss it out? – Caius Jard Mar 16 '21 at 05:33
  • This helped me. Your example did not have my additional cancellationToken in it, but it had the (). What I did was add your () with cancellationToken inside of it like this: ``` async (cancellationToken) => ``` and everything worked. I'd love to understand better what is going on. Why does it work without () when I run a single statement, but require () when I use a block? Thanks for your help! I am marking this as the answer. – Ryan Kayser Mar 17 '21 at 21:20