1

I have a Blazor Server C# application in .NET 9 and I use a service to handle remote API calls. I would like to set timeout for requests globally like this:

var timeoutSec = builder.Configuration.GetValue<int>("Main:ServiceTimeoutSec");

builder.Services.AddHttpClient<ApiService>((serviceProvider, client) =>
{
    var config = serviceProvider.GetRequiredService<IConfiguration>();
    var baseAddress = config["Api:ApiBaseAddress"] ?? "MISSING_BASE_ADDRESS";

    client.BaseAddress = new Uri(baseAddress);
    // HAS NO EFFECT -> client.Timeout = TimeSpan.FromSeconds(timeoutSec);
})
.AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(timeoutSec))); // API query timeout

The last line sets the Polly timeout.

When I set ServiceTimeoutSec in config to less than 30 seconds, then the Polly timeout works fine and throws TimeoutRejectedException. This is ok!

When I set ServiceTimeoutSec in config more than 30 seconds, for example 60 seconds, then the I get timeout exception after 30 seconds with this error message:

The operation didn't complete within the allowed timeout of '00:00:30'

Why is the code not using my 60 seconds setting in this case?

I use Visual Studio 2022 in development mode.

Thank you!

I try increase the client.Timeout but has no effect, so I commented out this line.

I check client.Timeout in service constructor, it shows the default 00:01:40.

I double check the DI and service, and the good HttpClient is constructed in the service.

Excepted result: when I set the timeout to 60 seconds, I would like use this timeout everywhere with this HttpClient instance.


I tried change SignalR connection timeouts:

Server side:

builder.Services.AddServerSideBlazor()
       .AddHubOptions(options =>
       {
           options.MaximumReceiveMessageSize = 102_400_000;

           options.KeepAliveInterval = TimeSpan.FromSeconds(80);
           options.ClientTimeoutInterval = TimeSpan.FromSeconds(160); // ≥ 2 × keep-alive
           options.HandshakeTimeout = TimeSpan.FromSeconds(10);
       })
       .AddCircuitOptions(options => { options.DetailedErrors = builder.Environment.IsDevelopment(); }); // Enable detailed errors

Client side:

<script src="_framework/blazor.web.js" autostart="false"></script>

<script>
    Blazor.start({
        configureSignalR: builder => builder
            .withKeepAliveInterval(80000)   // 80 s  – must match server
            .withServerTimeout(160000),     // 160 s – ≥ 2 × keep-alive

        reconnectionOptions: {
            maxRetries: 10,
            retryIntervalMilliseconds: 5000
        }
    });

    window.Blazor.defaultReconnectionHandler._reconnectCallback = () => {
        console.log("Blazor is reconnecting...");
    };
</script>

I have no any extra webserver configuration, this is a simple Blazor Web App template in NET 9. If I good know this is a default Kestrel. I tried change some Kestrel settings in appsettings.json:

"Kestrel": {
    "Limits": {
        "KeepAliveTimeout": "00:02:00",        
        "RequestHeadersTimeout": "00:00:45",   
    }                                          
} 

The result is always same, timeout coming after 30 or 90 secods regardless of my settings...

9
  • It might happen that the underlying webserver (Kestrel, IIS Express) shortcuts the request after 30 seconds. That could be altered via the executionTimeout inside the applicationhost.config or appsettings.json Commented Jun 4 at 13:00
  • @PeterCsala I add new testings scenarios to my post with your suggestion (I not found executionTimeout prop, only similar props) Commented Jun 5 at 9:26
  • 1
    @PeterCsala greetings from Hungary, I am one of your students about the MVC course. :) Commented Jun 5 at 9:39
  • hello :D Could you please share the exception name and some stack trace information as well? Commented Jun 5 at 12:37
  • 1
    @PeterCsala Sorry for the delay. I’ve already managed to solve the problem using a solution very similar to yours. One additional note: RemoveAllResilienceHandlers is obsolete and will be removed (if I remember correctly), so I created my own extension method with the same functionality. Everything is working fine now. Thank you! Commented Jun 30 at 12:40

1 Answer 1

1

I haven't used Blazor for awhile so, it was a bit suprise for me that the HttpClients are decorated with StandardResilienceHandlers in .NET 9. Gladly we can modify them or delete them for a given HttpClient.

Removing the default resilience handlers + V7 API

builder.Services.AddHttpClient<ApiService>((serviceProvider, client) =>
{
    var config = serviceProvider.GetRequiredService<IConfiguration>();
    var baseAddress = config["Api:ApiBaseAddress"] ?? "MISSING_BASE_ADDRESS";
    client.BaseAddress = new Uri(baseAddress);
})
.RemoveAllResilienceHandlers() //The new code
.AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(timeoutSec)));

Here we are removing the standard handlers and we are setting the Timeout via Polly V7 API.

Removing the default resilience handlers + V8 API

builder.Services.AddHttpClient<ApiService>((serviceProvider, client) =>
{
    var config = serviceProvider.GetRequiredService<IConfiguration>();
    var baseAddress = config["Api:ApiBaseAddress"] ?? "MISSING_BASE_ADDRESS";
    client.BaseAddress = new Uri(baseAddress);
})
.RemoveAllResilienceHandlers() 
.AddResilienceHandler("timeout", builder => builder.AddTimeout(TimeSpan.FromSeconds(timeoutSec))); //The new code

Here we are removing the standard handlers and we are setting the Timeout via Polly V8 API.

Reconfiguring the standard resilience handlers

builder.Services.AddHttpClient<ApiService>((serviceProvider, client) =>
{
    var config = serviceProvider.GetRequiredService<IConfiguration>();
    var baseAddress = config["Api:ApiBaseAddress"] ?? "MISSING_BASE_ADDRESS";
    client.BaseAddress = new Uri(baseAddress);
})
.RemoveAllResilienceHandlers()
.AddStandardResilienceHandler(options =>
{
    options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(300); //For all attempts in total
    options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(90); //Per retry attempt
});

Here we are removing the standard handlers and reregisting it with different timeout settings. Please bear in mind that there are validators (like this) which could cause runtime errors if the configured values do not align with the rules.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.