Error in foreach loop when displaying an ICollection of a one-to-many data model

Viewed 351

I'm a beginner and trying to learn Blazor + EF Core..I have tried different tutorials but I think I am missing something that's why I can't seem to understand what's going on.

First: I have a data model for Contact, where one contact can have many ContactPersons and ContactAddresses. I wrote the class like this:

public class Contact
{
        public int ID { get; set; }
        public string AccountNumber { get; set; }
        public string AccountName { get; set; }

        public ICollection<ContactPerson> ContactPersons { get; set; }
        public ICollection<ContactAddress> ContactAddresses { get; set; }

        public string NatureOfBusiness { get; set; }
        public string Industry { get; set; }
}

Second: I have a web service (api) controller where I return a single Contact (by id) that includes all of its ContactPersons and ContactAddresses, my code looks like this:

[HttpGet("GetContact/{id}")]
public async Task<ActionResult<Contact>> GetContact(int id)
{
            if (!ContactExists(id))
            {
                return NotFound();
            }

            var contact = await _context.Contact
                .Include(c => c.ContactPersons)
                .Include(c => c.ContactAddresses)
                    .ThenInclude(ca => ca.Zip)
                .FirstOrDefaultAsync(c => c.ID == id);

            if (contact == null)
            {
                return NotFound();
            }

            return contact;
}

Third: My razor page retrieves the data from the web service by using the following code:

@code {
    [Parameter] public int contactId { get; set; }
    Contact contact = new Contact();

    protected async override Task OnParametersSetAsync()
    {
        contact = await http.GetFromJsonAsync<Contact>($"api/Contact/GetContact/{contactId}");
    }
}

Fourth: I tried to display the Contact and its ContactPersons with the following code (razor page):

<dl class="row">
<dt class="col-sm-2">Account Number:</dt>
<dd class="col-sm-10">@contact.AccountNumber</dd>
<dt class="col-sm-2">Account Name:</dt>
<dd class="col-sm-10">@contact.AccountName</dd>
<dt class="col-sm-2">Nature of Business:</dt>
<dd class="col-sm-10">@contact.NatureOfBusiness</dd>
<dt class="col-sm-2">Industry:</dt>
<dd class="col-sm-10">@contact.Industry</dd>

<dt class="col-sm-2">Contact Persons</dt>
<dd class="col-sm-10">
    <table class="table table-striped">
        <thead>
            <tr>
                <th>ID</th>
                <th>Abbr</th>
                <th>Contact Number</th>
                <th>Email Address</th>
            </tr>
        </thead>
        <tbody>
            @foreach (ContactPerson contactperson in contact.ContactPersons)
            {
            <tr>
                <td>@contactperson.ID</td>
                <td>@contactperson.Abbr</td>
                <td>@contactperson.ContactNumber</td>
                <td>@contactperson.EmailAddress</td>
            </tr>
            }
        </tbody>
    </table>
</dd>

BUT: When I use the foreach loop to display the contact's ContactPersons, I get "An unhandled error has occured. Reload" in the browser...I found out it is in the foreach loop because if I remove the foreach loop code block, the page will display correctly without the contact's ContactPersons

Please help me learn what's going on?

Thank you very much!

1 Answers

Try this code...

Note: Don't instantiate the Contact object

    @code 
    {
    [Parameter] public int contactId { get; set; }
    Contact contact;

    protected async override Task OnParametersSetAsync()
    {
        contact = await http.GetFromJsonAsync<Contact> 
                 ($"api/Contact/GetContact/{contactId}");
    }
   }

And in the view portion of your component use the following:

<tbody>
@if (contact == null)
{
    <p><em>Loading...</em></p>
}
else
{
            @foreach (ContactPerson contactperson in contact.ContactPersons)
            {
            <tr>
                <td>@contactperson.ID</td>
                <td>@contactperson.Abbr</td>
                <td>@contactperson.ContactNumber</td>
                <td>@contactperson.EmailAddress</td>
            </tr>
            }
}
</tbody>

Note: The code above verify whether contact has a value or not. As long as contact is null (has no value), we display the message "Loading...", when the GetFromJsonAsync method returns, and contact contains data we display it in a table.

Note: You should always check for null. Learn the lifecycle methods and how Blazor is rendering, so you'll get a clearer idea why you should always check for null.

Related