I've recently started including Blazor components in my ASP.Net core web app. I've decided to split my components into multiple files using the pattern [ComponentName].razor and [ComponentName].razor.cs for all the C# coding.
Something annoying which has been popping up is all my state change notification calls are showing as errors. Now, these errors disappear when I build and run the app, but they still congest my error list window. These will disappear if I close and then open Visual Studio 2019, but they come back after a couple of minutes. What is going on and how can I fix these errors?
Cheers!
InvokeAsync highlighted as an error
Edit: Here's some code as requested. The offending lines are in TransactionTableView.razor.cs. 'await InvokeAsync(StateHasChanged)' shows up as an error.
TransactionTableView.razor
<table class="table data-table" id="taskstable">
<thead>
<tr>
@if (ShowSelect)
{
<th>
<input type="checkbox" @bind="SelectAllTransactions" @bind:event="oninput" @onclick="() => CheckAllTransactions(!SelectAllTransactions)"/>
Select all
</th>
}
@if (ShowParent)
{
<th>
Parent
</th>
}
<th>
Task
</th>
<th>
Status
</th>
<th>
Due Date
</th>
<th>
Completion Date
</th>
<th>
Amount Due
</th>
<th>
Amount Paid
</th>
<th>
Comments
</th>
</tr>
</thead>
<tbody class="tasks-body">
@if (Transactions != null)
{
@foreach (var transaction in DisplayTransactions)
{
<tr class="@transaction.AlertClassDisplay()">
@if (ShowSelect)
{
<td>
<input @bind="transaction.Select" type="checkbox" @oninput="() => SelectSingleTask(transaction.TransactionId)"/>
</td>
}
@if (ShowParent)
{
<td>
@transaction.TenementKey
</td>
}
<td>
@transaction.Description
</td>
<td class="task-status">
<a asp-action="Details" asp-controller="Alerts" method="get" asp-route-transactionId="@transaction.TransactionId">
@transaction.TransactionStatus
</a>
</td>
<td>
@transaction.DueDateDisplay()
</td>
<td>
@($"{transaction.CompletionDate:dd MMM yyyy}")
</td>
<td>
@transaction.AmountDue
</td>
<td>
@transaction.AmountPaid
</td>
<td>
@transaction.Comments
</td>
</tr>
}
}
</tbody>
</table>
TransactionTableView.razor.cs
partial class TransactionTableView
{
// Injected services
[Inject]
private ApplicationDbContext _context { get; set; }
// Data
public List<int> TenementIds { get; set; }
public List<TransactionViewModel> Transactions { get; set; }
public List<TransactionViewModel> DisplayTransactions { get; set; }
public List<Filter> Filters { get; set; } = new List<Filter>();
private bool ShowParent { get; set; } = true;
private bool ShowSelect { get; set; } = true;
private bool SelectAllTransactions { get; set; } = false;
public async Task Refresh()
{
var transactions = new List<Transaction>(_context.Transactions.Where(x => x.TenementId != null && TenementIds.Contains((int)x.TenementId)));
transactions = transactions.OrderBy(x => x.DueDate).ToList();
var models = new List<TransactionViewModel>();
transactions.ForEach(x =>
{
models.Add(x.GetViewModel(_context));
});
Transactions = models;
await RefreshFilters();
}
public async Task RefreshFilters()
{
var holder = Transactions;
if (Filters != null)
{
foreach (var filter in Filters)
{
if(filter.FilteringProperty.PropertyType == typeof(string))
{
holder = holder.Where(x => ((string)filter.FilteringProperty.GetValue(x)).ToLower().Contains(filter.Values.First().ToString().ToLower())).ToList();
}
if(filter.FilteringProperty.PropertyType == typeof(DateTime) || filter.FilteringProperty.PropertyType == (typeof(DateTime?)))
{
var dFilter = (DateFilter)filter;
if (dFilter.SelectedAfterDate)
{
holder = holder.Where(x => (DateTime)dFilter.FilteringProperty.GetValue(x) > dFilter.TargetDate).ToList();
}
else if (dFilter.SelectBeforeDate)
{
holder = holder.Where(x => (DateTime)dFilter.FilteringProperty.GetValue(x) < dFilter.TargetDate).ToList();
}
else if (dFilter.SelectBetweenDates)
{
holder = holder.Where(x =>
{
var targetDate = (DateTime)dFilter.FilteringProperty.GetValue(x);
return targetDate < dFilter.TargetDate && targetDate > (DateTime)dFilter.FirstTargetDate;
}).ToList();
}
}
if(filter.FilteringProperty.PropertyType == typeof(TransactionStatus))
{
var targetStatuses = filter.Values.Select(x => (TransactionStatus)x);
holder = holder.Where(x => targetStatuses.Contains(x.TransactionStatus)).ToList();
}
}
}
DisplayTransactions = holder;
await RefreshAllTaskSelector();
await InvokeAsync(StateHasChanged);
}
private async Task CheckAllTransactions(bool checkAll)
{
foreach(var displayTask in DisplayTransactions)
{
displayTask.Select = checkAll;
}
await InvokeAsync(StateHasChanged);
}
private async Task SelectSingleTask(int transactionId)
{
var task = DisplayTransactions.SingleOrDefault(x => x.TransactionId == transactionId);
task.Select = !task.Select;
if (DisplayTransactions.Any() && DisplayTransactions.Where(x => !x.Select).Count() == 0)
{
SelectAllTransactions = true;
}
else
{
SelectAllTransactions = false;
}
await InvokeAsync(StateHasChanged);
}
private async Task RefreshAllTaskSelector()
{
if (DisplayTransactions.Any() && DisplayTransactions.Where(x => !x.Select).Count() == 0)
{
SelectAllTransactions = true;
}
else
{
SelectAllTransactions = false;
}
}
private async Task DeleteSelected()
{
if (_context.ChangeTracker.HasChanges())
{
throw new ApplicationException("DB already has changes");
}
var targets = new List<TransactionViewModel>(DisplayTransactions.Where(x => x.Select));
foreach(var target in targets)
{
Transactions.Remove(target);
DisplayTransactions.Remove(target);
var transaction = _context.Transactions.SingleOrDefault(x => x.TransactionId == target.TransactionId);
_context.Transactions.Remove(transaction);
}
await _context.SaveChangesAsync();
await InvokeAsync(StateHasChanged);
}
private async Task CompleteSelected()
{
if (_context.ChangeTracker.HasChanges())
{
throw new ApplicationException("DB already has changes");
}
var targets = new List<TransactionViewModel>(DisplayTransactions.Where(x => x.Select));
foreach (var target in targets)
{
var dbTask = _context.Transactions.SingleOrDefault(x => x.TransactionId == target.TransactionId);
dbTask.CompletionDate = DateTime.Now;
dbTask.TransactionStatus = TransactionStatus.Completed;
target.CompletionDate = dbTask.CompletionDate;
target.TransactionStatus = dbTask.TransactionStatus;
var orig = Transactions.SingleOrDefault(x => x.TransactionId == target.TransactionId);
orig.CompletionDate = dbTask.CompletionDate;
orig.TransactionStatus = dbTask.TransactionStatus;
}
await _context.SaveChangesAsync();
await RefreshFilters();
await InvokeAsync(StateHasChanged);
}
private async Task ArchiveSelected()
{
if (_context.ChangeTracker.HasChanges())
{
throw new ApplicationException("DB already has changes");
}
var targets = new List<TransactionViewModel>(DisplayTransactions.Where(x => x.Select));
foreach (var target in targets)
{
var dbTask = _context.Transactions.SingleOrDefault(x => x.TransactionId == target.TransactionId);
dbTask.CompletionDate = DateTime.Now;
dbTask.TransactionStatus = TransactionStatus.Archived;
target.CompletionDate = dbTask.CompletionDate;
target.TransactionStatus = dbTask.TransactionStatus;
var orig = Transactions.SingleOrDefault(x => x.TransactionId == target.TransactionId);
orig.CompletionDate = dbTask.CompletionDate;
orig.TransactionStatus = dbTask.TransactionStatus;
}
await _context.SaveChangesAsync();
await RefreshFilters();
await InvokeAsync(StateHasChanged);
}
}
I've tried with a little test project and I can confirm that you can have 2 possible issue:
@inherits
pathComponentBase
If you want to separate the two files in two different folder you need to add inherits
, like here:
EmployeeData.razor in Pages folder
@page "/employee/{Id}"
@using TestMenu.Shared
@using TestMenu.Client.Services
@inherits TestMenu.Client.Pages.test.EmployeeData
<h3>Employee</h3>
Result data: <b>@employeeData</b>
EmployeeData.razor.cs in Pages/test folder
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using TestMenu.Client.Services;
using TestMenu.Shared;
namespace TestMenu.Client.Pages.test
{
public partial class EmployeeData: ComponentBase
{
[Inject] IEmployeeService EmployeeService { get; set; }
[Parameter]
public string Id { get; set; }
protected Employee Employee = new();
protected string employeeData;
protected override async Task OnInitializedAsync()
{
Employee = (await EmployeeService.GetEmployeeById(int.Parse(Id)));
employeeData = Employee.Name;
await InvokeAsync(StateHasChanged);
}
}
}
if you remove the : ComponentBase
from the code behind of EmployeeData.razor.cs, you receive an error like the one in your screenshot.