Blazor: Spent 5 Hours Debugging (API)

Photo by Ryan Snaadt on Unsplash

Blazor: Spent 5 Hours Debugging (API)

Intro

I've come to realize time and again, that it's very easy as a developer to underestimate the time a project will take. Was working on a not greater that 10 minutes feature the other day and guess how long I ended up working on it.

We all know the importance of taking a break while working, especially when stuck trying to code a feature. It helps us reflect and most of the time approach the problem in a different perspective. But, doing this taking a break thing is mostly far from our minds when we just wanna get it over with and move on to the next exciting feature in our projects.

I was doing a class project, and with the deadline coming in hot, taking some time off from my project was a distant tiny dent in what was going on in my mind.

The Bug

So, what was the problem? To recreate the problem, the first thing I did was create an API like this

    [Route("api/[controller]")]
    [ApiController]
    public class WordsController : ControllerBase
    {
        #region fields
        private readonly ApplicationDbContext _context;
        private readonly UserManager<Player> _userManager;
        #endregion

        #region methods
        public WordsController(ApplicationDbContext context, UserManager<Player> userManager)
        {
            _context = context;
            _userManager = userManager;
        }

        [HttpGet]
        public async Task<Player> GetCurrentPlayer()
        {
            var playerID = _userManager.GetUserId(User);
            var player = await _userManager.FindByIdAsync(playerID);

            return player;
        }
    }

I then tried to consume the api like this

        public async Task<Player> GetCurrentPlayer()
        {
            var requestUri = "api/words/GetcurrentPlayer";
            var player = await _httpClient.GetFromJsonAsync<Player>(requestUri);

            return player;
        }

And these were the errors the system was spitting out my way

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: '<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
System.Text.Json.JsonException: '<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
 ---> System.Text.Json.JsonReaderException: '<' is an invalid start of a value. LineNumber: 0 | BytePositionInLine: 0.
   at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes)
   at System.Text.Json.Utf8JsonReader.ConsumeValue(Byte marker)
   at System.Text.Json.Utf8JsonReader.ReadFirstToken(Byte first)
   at System.Text.Json.Utf8JsonReader.ReadSingleSegment()
   at System.Text.Json.Utf8JsonReader.Read()
   at System.Text.Json.Serialization.JsonConverter`1[[WordJumble.Shared.Player, WordJumble.Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, JsonReaderException ex)
   at System.Text.Json.Serialization.JsonConverter`1[[WordJumble.Shared.Player, WordJumble.Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadCore[Player](JsonConverter jsonConverter, Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadCore[Player](JsonReaderState& readerState, Boolean isFinalBlock, ReadOnlySpan`1 buffer, JsonSerializerOptions options, ReadStack& state, JsonConverter converterBase)
   at System.Text.Json.JsonSerializer.ContinueDeserialize[Player](ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack, JsonConverter converter, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.<ReadAllAsync>d__65`1[[WordJumble.Shared.Player, WordJumble.Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext()
   at System.Net.Http.Json.HttpContentJsonExtensions.<ReadFromJsonAsyncCore>d__4`1[[WordJumble.Shared.Player, WordJumble.Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext()
   at System.Net.Http.Json.HttpClientJsonExtensions.<GetFromJsonAsyncCore>d__13`1[[WordJumble.Shared.Player, WordJumble.Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext()
   at WordJumble.Client.Services.WordService.GetCurrentPlayer() in C:\Users\pato\source\repos\WordJumbleRB\Client\Services\WordService.cs:line 166
   at WordJumble.Client.Pages.DashboardPages.DashboardHome.OnInitializedAsync() in C:\Users\pato\source\repos\WordJumbleRB\Client\Pages\DashboardPages\DashboardHome.razor:line 31
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

Yeah, its huge and very obscure. The errors don't do a very good job of telling you where the problem is, let alone how to fix it. Through googling, lots of trial and error and a fair share of pulling my hair out, I finally made the application quit spitting out these errors.

The Solution

After I fixed the problem, I mostly felt relief, with a hint of 'wanna laugh at myself'. Why? The solution was right there under my nose (well, technically it was right infront of my face).

You see, to be able to use the GetCurrentPlayer http GET request, I was using this url "api/words/GetCurrentPlayer". And looking at the Route Attribute ([Route("api/[controller]")]), api coincides with api, words coincide with [controller], GetCurrentPlayer coincides with ??? That was the question.

So this is how the API should look for the GET request to work

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class WordsController : ControllerBase
    {
        #region fields
        private readonly ApplicationDbContext _context;
        private readonly UserManager<Player> _userManager;
        #endregion

        #region methods
        public WordsController(ApplicationDbContext context, UserManager<Player> userManager)
        {
            _context = context;
            _userManager = userManager;
        }

        [HttpGet]
        public async Task<Player> GetCurrentPlayer()
        {
            var playerID = _userManager.GetUserId(User);
            var player = await _userManager.FindByIdAsync(playerID);

            return player;
        }
    }

Now 'GetCurrentPlayer' coincides with [action].

Conclusion

When faced with a bug. First try fixing it yourself. If that doesn't work, take a step back from the problem and come back to it later afresh. If that doesn't work, try to look for similar problems online, in forums and do some general googling and hopefully, that's all the steps you will need.

For those of you using blazor, I hope this will save you some time banging your heads on the wall, trying to implement APIs.

Thanks