locked
Blazor Server + WebAPI/EFCore + NSwag - BtServer.ApiException: Could not deserialize the response body stream as System.Collections.Generic.ICollection`1[[BtServer.Transactio RRS feed

  • Question

  • User379720387 posted

    My solution has a Blazor Server and a WebAPI with NSwag project.

    The API runs on port 44317 and displays the Swagger interface. A request through Swagger returns the expected result.

    The Blazor Server runs on port 44391

    [2020-12-26T21:18:33.032Z] Information: Normalizing '_blazor' to 'https://localhost:44391/_blazor'.
    blazor.server.js:1 [2020-12-26T21:18:33.073Z] Information: WebSocket connected to wss://localhost:44391/_blazor?id=VXt7-sg0JrjdT-WShseaKA.
    blazor.server.js:19 [2020-12-26T21:18:37.785Z] Error: HTTP Response: 
     
     
     
    BtServer.ApiException: Could not deserialize the response body stream as System.Collections.Generic.ICollection`1[[BtServer.Transaction, BtServer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].
     
    Status: 200
    Response: 
     
     ---> Newtonsoft.Json.JsonSerializationException: Required property 'client' expects a non-null value. Path '[0]', line 1, position 1698.
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EndProcessProperty(Object newObject, JsonReader reader, JsonObjectContract contract, Int32 initialDepth, JsonProperty property, PropertyPresence presence, Boolean setDefaultValue)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(IList list, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, String id)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
       at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
       at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
       at Newtonsoft.Json.JsonSerializer.Deserialize[T](JsonReader reader)
       at BtServer.swaggerClient.ReadObjectResponseAsync[T](HttpResponseMessage response, IReadOnlyDictionary`2 headers) in C:\Users\Robert\source\Repos\BtServer\obj\swaggerClient.cs:line 226
       --- End of inner exception stack trace ---
       at BtServer.swaggerClient.ReadObjectResponseAsync[T](HttpResponseMessage response, IReadOnlyDictionary`2 headers) in C:\Users\Robert\source\Repos\BtServer\obj\swaggerClient.cs:line 233
       at BtServer.swaggerClient.TransactionAsync(CancellationToken cancellationToken) in C:\Users\Robert\source\Repos\BtServer\obj\swaggerClient.cs:line 90
       at BtServer.Pages.Transactions.OnInitializedAsync() in C:\Users\Robert\source\Repos\BtServer\Pages\Transactions.razor:line 75
       at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
       at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
    e.log @ blazor.server.js:19
    blazor.server.js:1 [2020-12-26T21:18:37.787Z] Information: Connection disconnected.
    Transactions:1 
    Found this on SO. The OP describes my exact same problem, and there is a response (but no answer):
    I have problems with autogenerated NSwag code too. try changing JsonConvert.DeserializeObject<ICollection<LocationDto> to JsonConvert.DeserializeObject<List<LocationDto>>
    Any suggestions would be appreciated as usual.

    My Razor page to display Transactions looks like this:

    @page "/Data/Transactions"
    
    @using BtServer.Data
    
    
    @inject IHttpClientFactory ClientFactory
    
    
    <h1>Transactions</h1>
    
    @if (transactions is null)
    {
        <p><em>Loading...</em></p>
    }
    else
    {
        <table class="table table-striped">
            <thead>
                <tr>
                    <th>Id</th>
                    <th>ClientId</th>
                    <th>Bill No.</th>
                    <th>Is Billed</th>
                    <th>Is Paid</th>
                    <th>Charge</th>
                    <th>Tax</th>
                    <th>Txn Date</th>
                    <th>Bill Date</th>
                    <th>Service</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var txn in transactions)
                {
                    <tr>
                        <td>@txn.TransactionId</td>
                        <td>@txn.ClientId</td>
                        <td>@txn.BillNo</td>
                        <td>@txn.IsBilled</td>
                        <td>@txn.IsPaid</td>
                        <td>@txn.Charge</td>
                        <td>@txn.Tax</td>
                        <td>@txn.Tdate</td>
                        <td>@txn.Bdate</td>
                        <td>@txn.Service</td>
                    </tr>
                }
                @*<Virtualize Items="locations" Context="location">
                    <ItemContent>
                        <tr>
                            <td>@locations.ClientId</td>
                            <td>@locations.CName</td>
                            <td>@locations.Location</td>
                            <td>@locations.State</td>
                            <td>@locations.OName</td>
                            <td>@locations.TheCount</td>                    
                        </tr>
                    </ItemContent>
                </Virtualize>*@
                
            </tbody>
        </table>
    }
    
    @code {
        private ICollection<BtServer.Transaction> transactions;
    
        protected override async Task OnInitializedAsync()
        {
            var httpClient = ClientFactory.CreateClient("ServerApi");
    
    
            
            swaggerClient client = new("https://localhost:44317", httpClient);
            transactions = await client.TransactionAsync();
        }
    
    
    }

    My startup class

    public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
    
            public IConfiguration Configuration { get; }
    
            // 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.AddRazorPages();
                services.AddHttpClient();
                services.AddServerSideBlazor();
                //services.AddScoped<>();
                services.AddCors(o => o.AddPolicy("Policy", builder => {
                    builder.AllowAnyOrigin()
                      .AllowAnyMethod()
                      .AllowAnyHeader();
                }));
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler("/Error");
                    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                    app.UseHsts();
                }
    
                app.UseHttpsRedirection();
                app.UseStaticFiles();
    
                app.UseRouting();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapBlazorHub();
                    endpoints.MapFallbackToPage("/_Host");
                });
            }
        }

    Saturday, December 26, 2020 9:32 PM

Answers

  • User475983607 posted

    You need to troubleshoot.  The first step is reading the error message. 

    BtServer.ApiException: Could not deserialize the response body stream... Newtonsoft.Json.JsonSerializationException: Required property 'client' expects a non-null value. Path '[0]', line 1, position 1698

    Clearly the "client" property is null but the serializer expects a value.  Review your code and figure out why "client" is null.

    Secondly, you are using somehting named Swagger Client and the code makes no logical sense as shown.  I expect an actual C# constructor, not an anonymous type.

    swaggerClient client = swaggerClient new("https://localhost:44317", httpClient);

    Is swaggerClient JS interopt?  If so why are making an HTTP request from the browser when you purposely built a Blaozr Server application.  As explained in one of your other threads, make the HTTP request from the server not the browser.  That's one of the main reason to use Blazor Server.  Why are you creating a second connection when you have a dedicated connection to the server?

    If you want to make HTTP requests from the browser then built a Blazor WASM application.  

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Sunday, December 27, 2020 1:53 PM

All replies

  • User475983607 posted

    You need to troubleshoot.  The first step is reading the error message. 

    BtServer.ApiException: Could not deserialize the response body stream... Newtonsoft.Json.JsonSerializationException: Required property 'client' expects a non-null value. Path '[0]', line 1, position 1698

    Clearly the "client" property is null but the serializer expects a value.  Review your code and figure out why "client" is null.

    Secondly, you are using somehting named Swagger Client and the code makes no logical sense as shown.  I expect an actual C# constructor, not an anonymous type.

    swaggerClient client = swaggerClient new("https://localhost:44317", httpClient);

    Is swaggerClient JS interopt?  If so why are making an HTTP request from the browser when you purposely built a Blaozr Server application.  As explained in one of your other threads, make the HTTP request from the server not the browser.  That's one of the main reason to use Blazor Server.  Why are you creating a second connection when you have a dedicated connection to the server?

    If you want to make HTTP requests from the browser then built a Blazor WASM application.  

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Sunday, December 27, 2020 1:53 PM
  • User379720387 posted

    NET5.0 has tighter integration with Swagger. A new Core WebAPI project, now comes with Swagger all set up.

    https://www.youtube.com/watch?v=nY-w9wPFEuY  22 minutes in it shows how the swaggerclient is used.

    Sunday, December 27, 2020 3:21 PM
  • User475983607 posted

    Fantastic.  I learned new syntax in .net 5.  Still, the error message clearly and openly states "client" is null but expected not to be null.  Why is client null?  Use a tool like PostMan to to make requests to the API end point to see if there are any errors.  Perhaps build a simple test to see if the issue is with your design or making the HTTP request.

    Sunday, December 27, 2020 3:46 PM
  • User379720387 posted

    Postman responds with 200 and shows the requested information.

    Sunday, December 27, 2020 8:17 PM
  • User-474980206 posted

    You are getting a deserialization error.  the response does match the api specified by swagger  or maybe it’s the error page. You can use a network sniffer, or add a middleware response logger.

    Sunday, December 27, 2020 8:36 PM
  • User379720387 posted

    I stopped using Swagger because I couldn't get it to work and reverted to this:

    BtServer.Transaction[] transactions;
    
        protected async override Task OnInitializedAsync()
        {
            var httpClient = ClientFactory.CreateClient("ServerApi");
            httpClient.BaseAddress = new Uri(@"https://localhost:44317/");
            httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
    
            transactions = await httpClient.GetFromJsonAsync<BtServer.Transaction[]>("api/Transaction");
        }

    While messing around with this to get it to work I learned that I was missing  public DbSet<Transaction> Transactions { get; set; }  in AppDbContext.

    Then it started working.

    Since I have somehow messed up Swagger, and can no longer use it. Started using HttpRepl, which let's me test the api from a command line.

    Wednesday, December 30, 2020 4:39 PM