I'm developing a blazor web app and I'm facing a weird behaviour. Almost all pages are a copy/paste, they have CRUD operations and a previous list of differents elements (seasons, player, fixtures, etc...) but only in one of them I'm having problems. When I click on the 'Edit' button of a player I'm getting a 'Cannot access a disposed object' referring to the _dbContext.
Adding a breakpoint to the DisposeAsync() method I've discovered that it's been fired when I navigate to the edit page. But only happens in player's page, not in any of the other pages.
Here's the players page:
@page "/players"@attribute [Authorize]@using Models = Biwenger.Models@using Biwenger.Services@inject PlayersService service;@inject NavigationManager navigationManager;<h3>Jugadores</h3><a class="btn btn-primary" href="/player" role="button">Añadir</a><div class="mb-3 lg-6"><label for="search" class="form-label">Buscar Jugador:</label><input type="text" id="search" class="form-control" @bind="searchTerm" @oninput="FilterPlayers" placeholder="Escribe el nombre del jugador..." /></div><table class="table"><thead><tr><th scope="col">#</th><th scope="col">Nombre</th><th scope="col">Equipo</th><th scope="col">Acciones</th></tr></thead><tbody> @if (filteredList.Count > 0) { foreach (var item in filteredList) {<tr><th scope="row">@item.Id</th><td>@item.Name</td><td>@item.Team.Name</td><td><button type="button" class="btn btn-primary" @onclick="() => EditPlayer(item.Id, item.TeamId)">Editar</button></td></tr> } } else {<tr><td colspan="4" class="text-center">No hay registros.</td></tr> }</tbody></table>@code { List<Models.Player> fullList = new List<Models.Player>(); List<Models.Player> filteredList = new List<Models.Player>(); string searchTerm = ""; protected override async Task OnInitializedAsync() { fullList = await service.GetAllPlayers(); filteredList = fullList; } private void FilterPlayers() { if (string.IsNullOrEmpty(searchTerm)) { filteredList = fullList; } else { filteredList = fullList.Where(p => p.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) .ToList(); } StateHasChanged(); } private void EditPlayer(int id, int teamId) { navigationManager.NavigateTo($"/player/{id}/{teamId}"); <-- Here calls for dispose }}This is the page I'm navigating to:
@page "/player/{id:int?}/{teamId:int?}"@attribute [Authorize]@using Biwenger.Models.ViewModels@using Biwenger.Services@inject ILogger<Player> Logger@inject PlayersService service@inject TeamsService teamsService@inject SeasonsService seasonsService;@inject NavigationManager navigationManager@inject IJSRuntime JS<PageTitle>Equipo</PageTitle><h3>@(model?.PlayerId == 0 ? "Nuevo Jugador" : "Editar Jugador")</h3><EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="form"><DataAnnotationsValidator /><div class="col-mb-3 col-lg-6 col-md-6"><label for="name" class="form-label">Nombre</label><input id="name" class="form-control" @bind="model!.Name" @onblur="CheckIfNameExists" @ref="nameInput"></input></div><div class="col-mb-3 col-lg-6"><ValidationMessage For="() => model!.Name"></ValidationMessage></div><div class="col-mb-3 col-lg-6 col-md-6"><label for="team" class="form-label">Equipo</label><InputSelect id="team" class="form-control" @bind-Value="model!.TeamId"><option value="">Seleccione un equipo</option> @foreach (Models.Team team in listTeams) {<option value="@(team.Id)">@team.Name</option> }</InputSelect></div><div class="col-mb-3 col-lg-6"><ValidationMessage For="() => model!.TeamId"></ValidationMessage></div><div class="col-mb-3 col-lg-6 col-md-6"><label for="team" class="form-label">Posición</label> @if (positionsItems != null) {<BootstrapSelect TItem="Models.DropdownItem<String>" Data="@positionsItems" @bind-Value="model!.Position" TextField="@((item) => item.Value)" ValueField="@((item) => item.Key.ToString())" TType="Biwenger.Enums.Positions"></BootstrapSelect> }</div><div class="col-mb-3 col-lg-6"><ValidationMessage For="() => model!.TeamId"></ValidationMessage></div><div class="col-mb-3 col-lg-6 col-md-6"><label for="cost" class="form-label">Coste @currentSeason!.Name</label><input type="number" id="cost" @bind="currentPlayerCost!.Cost" min="1" class="form-control text-end" @ref="costInput" @onfocus="selectAllText" @onblur="addMillions" /></div><div class="form-check"><input class="form-check-input" type="checkbox" value="@model!.Black" id="checkBlack" @bind="model!.Black" /><label class="form-check-label" for="checkBlack"> Es negro</label></div><div class="form-check"><input class="form-check-input" type="checkbox" value="@model!.Active" id="checkActive" @bind="model!.Active" /><label class="form-check-label" for="checkActive"> Activo</label></div><div class="col-mb-3 col-lg-6"><button type="button" class="btn btn-secondary ms-2" @onclick="GoBack">Volver</button><button type="submit" class="btn @buttonClass" disabled="@(!editContext?.Validate() ?? false || isLoading)"> @if (isLoading) {<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span><span class="visually-hidden">Enviando...</span> } else if (showSuccess) {<span>Guardado...</span> } else if (showError) {<span>Error...</span> } else {<span>Enviar</span> }</button></div></EditForm>@code { private EditContext? editContext; private ElementReference nameInput; private ElementReference costInput; private Biwenger.Models.Season? currentSeason; private Biwenger.Models.PlayerSeasonCost? currentPlayerCost; [Parameter] public int? id { get; set; } [Parameter] public int? teamId { get; set; } [SupplyParameterFromForm] private PlayerWithCostViewModel? model { get; set; } private List<Models.Team> listTeams = new List<Models.Team>(); private ValidationMessageStore? messageStore; private bool isLoading = false; private bool showSuccess = false; private bool showError = false; private string buttonClassSuccess = "btn-success"; private string buttonClassError = "btn-danger"; private string buttonClassIdle = "btn-primary"; private string buttonClass = "btn-primary"; IList<Models.DropdownItem<String>> positionsItems; protected override async Task OnInitializedAsync() { positionsItems = new List<Models.DropdownItem<String>> { new Models.DropdownItem<String> { Key = 0, Value = "Posición" }, new Models.DropdownItem<String> { Key = 1, Value = "PT" }, new Models.DropdownItem<String> { Key = 2, Value = "DF" }, new Models.DropdownItem<String> { Key = 3, Value = "MC" }, new Models.DropdownItem<String> { Key = 4, Value = "DL" }, }; editContext = new EditContext(model ??= new PlayerWithCostViewModel()); currentSeason = seasonsService.GetCurrentSeason(); currentPlayerCost = new Models.PlayerSeasonCost(); listTeams = await teamsService.GetAllTeams(); if (id.HasValue && id.Value > 0) { model = await service.GetPlayerWithCostById(id.Value, teamId!.Value); if (model != null) { editContext = new EditContext(model); } } messageStore = new ValidationMessageStore(editContext); } private async void CheckIfNameExists() { if (!string.IsNullOrEmpty(model!.Name)) { bool exists = await service!.NameExists(model!.Name, id, model!.TeamId); messageStore?.Clear(() => model.Name); if (exists) { messageStore?.Clear(); messageStore?.Add(() => model.Name, "El nombre ya existe"); } editContext?.NotifyValidationStateChanged(); } } private void GoBack() { navigationManager.NavigateTo("/players"); } private async Task Submit() { isLoading = true; Logger.LogInformation("Se ha llamado a submit"); bool success = false; if (editContext!.Validate()) { if (id.HasValue && id.Value > 0) { success = await service.UpdatePlayer(model!, currentSeason!.Id); } else { if (model!.Costs!.Count == 0) { model!.Costs.Add(new Models.PlayerSeasonCost() { Cost = currentPlayerCost!.Cost, SeasonId = currentSeason!.Id, TeamId = model!.TeamId }); } success = await service!.AddPlayer(model!); } isLoading = false; if (success) { showSuccess = true; buttonClass = buttonClassSuccess; StateHasChanged(); await Task.Delay(3000); showSuccess = false; buttonClass = buttonClassIdle; StateHasChanged(); if (!id.HasValue) { model = new PlayerWithCostViewModel(); editContext = new EditContext(model); messageStore = new ValidationMessageStore(editContext); messageStore.Clear(); } model!.Name = ""; model.TeamId = 0; currentPlayerCost = new Models.PlayerSeasonCost(); model!.Black = false; model!.Active = true; StateHasChanged(); await JS.InvokeVoidAsync("focusElement", nameInput); } else { showSuccess = true; buttonClass = buttonClassError; await Task.Delay(1000); showSuccess = false; buttonClass = buttonClassIdle; } } } private async Task selectAllText() { await JS.InvokeVoidAsync("selectElementText", costInput); } private void addMillions() { if (currentPlayerCost!.Cost < 100) { currentPlayerCost.Cost = currentPlayerCost.Cost * 1000000; } }}And the playersService:
using Biwenger.Data;using Biwenger.Models;using Biwenger.Models.ViewModels;using Microsoft.EntityFrameworkCore;namespace Biwenger.Services{ public class PlayersService { private readonly ApplicationDbContext _dbContext; public PlayersService(ApplicationDbContext dbContext) { _dbContext = dbContext; } public async Task<List<Player>> GetAllPlayers() { return await _dbContext.Players.Include(p => p.Team).AsNoTracking().ToListAsync(); } public async Task<List<Player>> GetAllActivePlayers() { return await _dbContext.Players.Where(p => p.Active == true).AsNoTracking().ToListAsync(); } public async Task<bool> AddPlayer(PlayerWithCostViewModel player) { var strategy = _dbContext.Database.CreateExecutionStrategy(); bool result = false; await strategy.ExecuteAsync(async () => { var transaction = await _dbContext.Database.BeginTransactionAsync(); try { var newPlayer = new Player { Active = player.Active, Black = player.Black, TeamId = player.TeamId, Name = player.Name, Position = player.Position, }; await _dbContext.Players.AddAsync(newPlayer); var playerSeasonCost = new PlayerSeasonCost { Cost = player.Costs!.First().Cost, TeamId = player.TeamId, PlayerId = newPlayer.Id, SeasonId = player.Costs!.First().SeasonId, }; await _dbContext.PlayersSeasonsCost.AddAsync(playerSeasonCost); await _dbContext.SaveChangesAsync(); await transaction.CommitAsync(); result = true; } catch (Exception ex) { await transaction.RollbackAsync(); result = false; } }); return result; } public async Task<bool> UpdatePlayer(PlayerWithCostViewModel player, int seasonId) { Player? currentPlayer = await _dbContext.Players.FindAsync(player.PlayerId); if (currentPlayer == null) { return false; } var transaction = await _dbContext.Database.BeginTransactionAsync(); try { currentPlayer.Name = player.Name; currentPlayer.Black = player.Black; currentPlayer.Position = player.Position; currentPlayer.Active = player.Active; currentPlayer.TeamId = player.TeamId; _dbContext.Players.Update(currentPlayer); await _dbContext.SaveChangesAsync(); await transaction.CommitAsync(); return true; } catch (Exception ex) { await transaction.RollbackAsync(); return false; } } public async Task<Player?> GetPlayerByid(int id) { return await _dbContext.Players.FindAsync(id); } public async Task<PlayerWithCostViewModel?> GetPlayerWithCostById(int playerId, int teamId) { try { Player? player = await _dbContext.Players .Include(p => p.PlayersSeasonsCost) .Where(p => p.Id == playerId && p.TeamId == teamId) .FirstOrDefaultAsync(); <-- Already _dbContext Disposed if (player == null) { return null; } List<PlayerSeasonCost>? pscList = await _dbContext.PlayersSeasonsCost.Where(psc => psc.PlayerId == playerId).AsNoTracking().ToListAsync(); PlayerWithCostViewModel pwcvm = new PlayerWithCostViewModel { PlayerId = playerId, TeamId = teamId, Active = player.Active, Black = player.Black, Position = player.Position, Name = player.Name, Costs = pscList ??= new List<PlayerSeasonCost>() }; return pwcvm; } catch (Exception ex) { Console.WriteLine(ex.Message); return null; } } public async Task<bool> NameExists(string name, int? id, int teamId) { if (id.HasValue && id.Value > 0) { return await _dbContext.Players.AnyAsync(t => t.Name == name && t.TeamId == teamId && t.Id != id); } return await _dbContext.Players.AnyAsync(t => t.Name == name && t.TeamId == teamId); } public async Task<List<PlayerWithCurrentCostVM>> GetPlayersWithCurrentCost() { List<PlayerWithCurrentCostVM> pwcc = new List<PlayerWithCurrentCostVM>(); pwcc = await _dbContext.Players.Include(p => p.PlayersSeasonsCost) .ThenInclude(psc => psc.Season) .Where(p => p.Active == true) .Select(p => new PlayerWithCurrentCostVM { Player = p, Name = p.Name, Cost = p.PlayersSeasonsCost == null ? 0 : p.PlayersSeasonsCost.Where(psc => psc.Season.Active == true).First().Cost }) .AsNoTracking() .ToListAsync(); return pwcc; } }}