Skip to content

Error 503. The service is unavailable (blazor server side) #10012

@islandmanllc

Description

@islandmanllc

Describe the bug

Using HttpClient multiple times in a row causes responses to start lagging and eventually return HTTP Error 503. The service is unavailable despite the api service reporting no issues at all and keeps responding to other calls.
Asp.net core version: 3.0.100-preview4-011223

To Reproduce

Please note that the code is a bit messy due to troubleshooting. Here's the code:
The service that is injected into the .razor page

public async Task<PagedResult<ProductViewModels.Details>> GetProductsAsync(int? pageIndex, int itemsPerPage)
        {
            var url = $"{_baseUrl}/products/list/{pageIndex},{itemsPerPage}";
            try
            {
                var response = await _client.GetAsync(url);

                if (response.IsSuccessStatusCode)
                {
                    var jsonString = await response.Content.ReadAsStringAsync();
                    var jObj = (JObject) JsonConvert.DeserializeObject(jsonString);

                    var paged =
                        JsonConvert.DeserializeObject<PagedResult<ProductViewModels.Details>>(
                            jObj["payLoad"].ToString());

                    return paged;
                }
                else
                {
                    var msg =
                        $"Unsuccessful request to {url} {response.StatusCode} {await response.Content.ReadAsStringAsync()}";
                    _logger.LogError(
                        msg);
                    throw new Exception(msg);
                }
            }
            catch (Exception e)
            {
                _logger.LogError($"Exception when trying to get product records from {url}", e);
                throw;
            }
        }`

The .razor page:

@page "/products"
@using Kiss.Application.Features.Products.ViewModels
@using Microsoft.AspNetCore.Components

<DataTable Items="@_products" TItem="ProductViewModels.Details">
    <HeaderTemplate>
        <th>Name</th>
        <th>Description</th>
    </HeaderTemplate>
    <RowTemplate Context="Item">
        <td>@Item.Name</td>
        <td>@Item.Description</td>
    </RowTemplate>
    <FooterTemplate Context="Item">
        <td colspan="2">@_itemsTotal products found.</td>
    </FooterTemplate>
</DataTable>
<Paginator ref="_paginator" LastPageIndex="@_lastPageIndex" />

@functions {
    private int _itemsPerPage = 5;
    private int _lastPageIndex = 0;

    private IEnumerable<ProductViewModels.Details> _products = new List<ProductViewModels.Details>();
    private int _itemsTotal = 0;

    private Paginator _paginator = new Paginator();

    [Inject] IProductService ProductService { get; set; }

    protected override void OnAfterRender()
    {
        _paginator.PageChanged += PageChangedEvent;
    }

    public async void PageChangedEvent(int pageIndex)
    {
        await UpdateProducts(pageIndex, _itemsPerPage);
    }

    protected override async Task OnInitAsync()
    {
        await UpdateProducts(0, _itemsPerPage);
    }

    private async Task UpdateProducts(int pageIndex, int itemsPerPage)
    {
        if (ProductService != null)
        {
            var result = await ProductService.GetProductsAsync(pageIndex, itemsPerPage);
            if (result != null)
            {
                _products = result.Items;
                _itemsTotal = result.ItemsTotal;
                _itemsPerPage = result.ItemsPerPage;
                _lastPageIndex = result.TotalPages - 1;
                StateHasChanged();
            }
        }
    }
}

The paginator that triggers the api calls

@using Microsoft.AspNetCore.Components
<div class="dataTables_paginate paging_simple_numbers" id="DataTables_Table_0_paginate">
    <ul class="pagination">
        <li class="paginate_button page-item previous @Disabled(0)" id="DataTables_Table_0_previous">
            <a aria-controls="DataTables_Table_0" data-dt-idx="0" tabindex="0" class="page-link" onclick="@FirstPage">First</a>
        </li>
        <li class="paginate_button page-item previous @Disabled(0)" id="DataTables_Table_0_previous">
            <a aria-controls="DataTables_Table_0" data-dt-idx="1" tabindex="0" class="page-link" onclick="@PreviousPage">Previous</a>
        </li>

        <li class="paginate_button page-item next @Disabled(LastPageIndex)" id="DataTables_Table_0_next">
            <a aria-controls="DataTables_Table_0" data-dt-idx="2" tabindex="0" class="page-link" onclick="@NextPage">Next</a>
        </li>
        <li class="paginate_button page-item next @Disabled(LastPageIndex)" id="DataTables_Table_0_next">
            <a aria-controls="DataTables_Table_0" data-dt-idx="3" tabindex="0" class="page-link" onclick="@LastPage">Last</a>
        </li>
    </ul>
</div>

@functions
{
    private int _currentPageIndex = 0;

    [Parameter]
    private int LastPageIndex { get; set; }

    [Parameter]
    public Action<int> PageChanged { get; set; }

    void ChangeCurrentPageIndex(int pageIndex)
    {
        if (pageIndex != _currentPageIndex)
        {
            _currentPageIndex = pageIndex;
            PageChanged?.Invoke(_currentPageIndex);
        }
    }

    string Disabled(int pageIndex)
    {
        return _currentPageIndex == pageIndex ? "disabled" : "";
    }

    void FirstPage()
    {
        ChangeCurrentPageIndex(0);
    }

    void NextPage()
    {
        if (_currentPageIndex < LastPageIndex)
        {
            ChangeCurrentPageIndex(_currentPageIndex + 1);
        }
    }

    void PreviousPage()
    {
        if (_currentPageIndex > 0)
        {
            ChangeCurrentPageIndex(_currentPageIndex - 1);
        }
    }

    void LastPage()
    {
        ChangeCurrentPageIndex(LastPageIndex);
    }
}

Startup.cs:

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHttpClient();
            services.AddScoped<HttpClient>(s =>
            {
                // Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
                var uriHelper = s.GetRequiredService<IUriHelper>();
                return new HttpClient
                {
                    BaseAddress = new Uri(uriHelper.GetBaseUri())
                };
            });
            services.AddScoped<IProductService, ProductService>();

            services.AddRazorPages();
            services.AddServerSideBlazor();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            loggerFactory.AddSerilog();
            app.UseHttpsRedirection();

            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }
    }

Symptom

After the page loads the table of records loads correctly. When clicking on Next and Previous repeatedly to go back and forth (not particularly fast), it takes about 3 iterations for the response to start lagging and eventually stop with an error. I hard coded the api endpoint and tried it in isolation and it's responding very quickly without any issues (hardcoded or not). I've searched a lot to see if my setup is incorrect but can't find anything that would suggest so at this point. One minor thing to note is that the event handler in the products.razor page is async void, which I thought may be a reason.

Exceptions Thrown

[2019-05-06T20:23:43.734Z] Error: System.Exception: Unsuccessful request to http://localhost/kiss.api/products/list/0,5 ServiceUnavailable <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Service Unavailable</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Service Unavailable</h2>
<hr><p>HTTP Error 503. The service is unavailable.</p>
</BODY></HTML>

   at Kiss.Web.Services.ProductService.GetProductsAsync(Nullable`1 pageIndex, Int32 itemsPerPage) in C:\dev\TVProjects\Kiss\Kiss.Web\Services\ProductService.cs:line 54
   at Kiss.Web.Pages.Products.UpdateProducts(Int32 pageIndex, Int32 itemsPerPage) in C:\dev\TVProjects\Kiss\Kiss.Web\Pages\Products.razor:line 50
   at Kiss.Web.Pages.Products.PageChangedEvent(Int32 pageIndex) in C:\dev\TVProjects\Kiss\Kiss.Web\Pages\Products.razor:line 38
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__139_0(Object state)
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteSynchronously(TaskCompletionSource`1 completion, SendOrPostCallback d, Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteBackground(WorkItem item)

and

blazor.server.js:1 Uncaught (in promise) Error: Cannot send data if the connection is not in the 'Connected' State.
    at e.send (blazor.server.js:1)
    at e.sendMessage (blazor.server.js:1)
    at e.send (blazor.server.js:1)
    at Object.beginInvokeDotNetFromJS (blazor.server.js:8)
    at c (blazor.server.js:8)
    at Object.s [as invokeMethodAsync] (blazor.server.js:8)
    at blazor.server.js:8
    at e.onEvent (blazor.server.js:8)
    at e.onGlobalEvent (blazor.server.js:8)

Expected behavior

Since the api service is working fine, the expectation is that I should be able to use the pagination to go back and forth without a degradation/lagging and eventually error that requires a page reload.

Screenshots

image

Additional context

This is the first time I post on GitHub, hope I did it right and hope for a solution. Apologies if I made a mistake here.

Thanks

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions