asp.netrazor-pagesviewdata

Handling multiple items of data on the same view page


I have 3 pages on a app I am writing. It is historical, view Rabies Vaccine info only.

  1. Owner Info - has a view list of pets owned
  2. Pet Detail Info - has a view list of vaccines received by that particular pet
  3. A printable rabies certificate view for a particular pet/vaccine combo

I had posted here asking how to get the data passed from page 2 to page 3 on this post (Pass Data From One Razor Page to Another Razor Page) and it was quickly answered and I was able to get that working.

But when I add the info to get the data to pass from page 1 to page 2, I get an error on the

ViewData["petid1"] = Pet.PetId; 

line from passing data from page 2 to 3. When I bring up page 1 and hover over my button to take me to Page 2 the proper Owner Id and Pet Id are showing up in the hover, but actually clicking the button throws the error at

ViewData["petid1"] = Pet.PetId;

Here are the parts of my views and .cs in question:

Owner View:

<td width="20%">
    <div class="w-75 btn-group" role="group">
        <a asp-page="/Pets/Details" class="btn btn-primary mx-2" 
           asp-route-ownerid="@ViewData["ownerid1"]" 
           asp-route-ownerspetid="@Model.Owner.OwnersPets[i].PetId">
            <i class="bi bi-printer"></i>
        </a>
    </div>
</td>

Owner.cs:

using RabiesClinics.Data;
using RabiesClinics.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;

namespace RabiesClinics.Pages.Owners;

[BindProperties]

public class DetailsModel : PageModel
{
    private readonly ApplicationDbContext _db;

    public Owner Owner { get; set; }
    public Pet Pet { get; set; }

    public DetailsModel(ApplicationDbContext db)
    {
        _db = db;
    }

    public async Task OnGetAsync(int Id)
    {
        Owner = _db.Owner
            .Include(owner => owner.OwnersPets).FirstOrDefault(owner => owner.OwnerId == Id);

        ViewData["ownerid1"] = Owner.OwnerId;
    }
}

Owner model class:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RabiesClinics.Model
{
    public class Owner
    {
        [Key]
        [Required]
        public int OwnerId { get; set; }

        [ForeignKey("OwnerId")]
        public List<Pet>? OwnersPets { get; set; }
    }
}

Pet view:

@page
@using RabiesClinics.Model
@model RabiesClinics.Pages.Pets.DetailsModel

<form method="post">
    <div class="border p-3 mt-4">
        <div class="row pb-2">
            <h2 class="text-primary pl-3">Pet Details <img src="~/Images/PawPrints.png" /></h2>
            <hr />
        </div>
        <table class="table table-borderless" style="width:100%">
        <tr>
            <td style="width: 20%">
                <div class="mb-3">
                    <label asp-for="Pet.OwnerId"></label>
                    <input asp-for="Pet.OwnerId" name="ownerid" disabled class="form-control" />
                </div>
            </td>
            <td style="width: 20%">
                <div class="mb-3">
                        <label asp-for="Pet.PetId"></label>
                        <input asp-for="Pet.PetId" name="ownerspetid" disabled class="form-control" />
                </div>
            </td>
            <td style="width: 20%">
                <div class="mb-3">
                    <label asp-for="Pet.PetName"></label>
                    <input asp-for="Pet.PetName" type="text" disabled class="form-control"/>
                </div>
            </td>
            <td style="width: 20%">
                <div class="mb-3">
                    <label asp-for="Pet.LastVaxDate"></label>
                    <input asp-for="Pet.LastVaxDate" type="text" disabled class="form-control" />
                </div>
            </td>
            <td style="width: 20%">
                <div class="form-group, mb-3">
                    <label asp-for="Pet.Status"></label>
                    <br />
                    @foreach (var item1 in Html.GetEnumSelectList<Status>())
                    {
                        <input type="radio" disabled asp-for="Pet.Status" value="@item1.Text" />
                    @item1.Text
                    }
                </div>
            </td>
        </tr>
        </table>
        <table class="table table-borderless" style="width:100%">
        <tr>
            <td style="width: 20%">
                <div class="form-group, mb-3">
                    <label asp-for="Pet.Species"></label>
                    <br />
                    @foreach (var item2 in Html.GetEnumSelectList<Species>())
                    {
                        <input type="radio" disabled asp-for="Pet.Species" value="@item2.Text" />
                        @item2.Text
                    }
                </div>
            </td>
            <td style="width: 20%">
                <div class="form-group, mb-3">
                    <label asp-for="Pet.Gender"></label>
                <br />
                    @foreach (var item3 in Html.GetEnumSelectList<Gender>())
                    {
                        <input type="radio" disabled asp-for="Pet.Gender" value="@item3.Text" />
                        @item3.Text
                    }
                </div>
            </td>
            <td style="width: 20%">
                <div class="form-group, mb-3">
                    <label asp-for="Pet.Altered"></label>
                    <br />
                        <input asp-for="Pet.Altered" type="checkbox" disabled data-val="true" id="Pet.Altered" name="Pet.Altered" value="true" />
                        <input name="Pet.Altered" type="hidden" value="false" />
                </div>
            </td>
            <td style="width: 20%">
                <div class="form-group, mb-3">
                    <label asp-for="Pet.Age"></label>
            <br />
                    @foreach (var item4 in Html.GetEnumSelectList<Age>())
                    {
                        <input type="radio" disabled asp-for="Pet.Age" value="@item4.Text" />
                        @item4.Text
                    }
                </div>
            </td>
            <td style="width: 20%">
                <div class="mb-3">
                    <label asp-for="Pet.Colors"></label>
                    <input asp-for="Pet.Colors" type="text" disabled class="form-control" />
                </div>
            </td>
        </tr>
    </table>
    <table class="table table-borderless" style="width:60%">
        <tr>
            <td style="width: 40%">
                <div class="mb-3">
                    <label asp-for="Pet.Breed"></label>
                    <input asp-for="Pet.Breed" type="text" disabled class="form-control" />
                </div>
            </td>
            <td style="width: 20%">
                <div class="form-group, mb-3">
                    <label asp-for="Pet.Mix"></label>
                    <br />
                    <input asp-for="Pet.Mix" type="checkbox" disabled data-val="true" id="Pet.Mix" name="Pet.Mix" value="true" />
                    <input name="Pet.Mix" type="hidden" value="false" />
                </div>
            </td>
        </tr>
    </table>

        <hr />
        <h4>Vaccines</h4>
        <br />
        <table class="table table-bordered table-striped width:100%">
            <thead>
                <tr>
                    <th>
                        Tag No
                    </th>
                    <th>
                        Date Vaxxed
                    </th>
                    <th>
                        Print Rabies Certificate
                    </th>
                </tr>
            </thead>
            <tbody>
                @for (var i = 0; i < Model.Pet.Vaccines.Count; i++)
                {
                    <tr>
                        <td style="width: 10%">
                            <div class="mb-3">
                                <input asp-for="Pet.Vaccines[i].RabiesTagNo" type="text" readonly class="form-control-plaintext" />
                            </div>
                        </td>
                        <td style="width: 10%">
                            <div class="mb-3">
                                <input asp-for="Pet.Vaccines[i].DateVaccinated" type="text" readonly class="form-control-plaintext" />
                            </div>
                        </td>
                        <td width="10%">
                            <div class="w-75 btn-group" role="group">
                                <!--Line below is good.-->
                                <a asp-page="/RabiesCertificates/Details" class="btn btn-primary mx-2" asp-route-petid="@ViewData["petid1"]" asp-route-vaxdate="@Model.Pet.Vaccines[i].DateVaccinated">
                                    <i class="bi bi-printer"></i>
                                </a>
                            </div>
                        </td>
                    </tr>
                }
        </table>
        <div>
            <a asp-page="Index" class="btn btn-secondary" style="width: 150px;">Back to List</a>
        </div>
    </div>
</form>

@section Scripts {
    <partial name="_ValidationScriptsPartial"/>
}

Pet.cs:

using RabiesClinics.Data;
using RabiesClinics.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;

namespace RabiesClinics.Pages.Pets;

[BindProperties]

public class DetailsModel : PageModel
{
    private readonly ApplicationDbContext _db;

    public Pet Pet { get; set; }
    public Vaccine Vaccine { get; set; }

    public DetailsModel(ApplicationDbContext db)
    {
        _db = db;
    }

    public void OnGet(int ownerid, string ownerspetid, string Id)
    {
        var ownerid1 = ownerid;
        var petid2 = ownerspetid;

        Pet = _db.Pet
            .Include(pet => pet.Vaccines).FirstOrDefault(pet => pet.PetId == Id);

        //Line below was working before adding ownerid and ownerspetid.
        ViewData["petid1"] = Pet.PetId;
    }
}

Pet model class:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RabiesClinics.Model
{
    public class Pet
    {
        [Key]
        public string PetId { get; set; }

        [ForeignKey("OwnerId")]
        [Display(Name = "Owner Id")]
        public int OwnerId { get; set; }
        public Owner Owner;

        public List<Vaccine>? Vaccines { get; set; }

        [Display(Name = "Pet Name")]
        public string PetName { get; set; }
    }
}

Rabies cert view:

@page
@model RabiesClinics.Pages.RabiesCertificates.DetailsModel

<div id="RabiesCertificateReports" class="container p-3">
    <div class="row pt-4">
        <div class="col-6">
            <h2 class="text-primary">Rabies Certificate</h2>
        </div>
    </div>

    <br /><br />

    <form asp-page="./Details" method="get">
        <div class="form-actions no-color">
            <p>
                <h5>Report Date Range</h5>
                For Pet Id: <input type="text" name="petid" value="" /> and Vaccine Date:<input type="date" name="vaxdate" value="" />
                <input type="submit" value="Filter Report" class="btn btn-primary mx-2" />
                <a asp-page="./Pets/Detail" class="btn btn-link mx-2">Return to Pet Detail</a>
            </p>
        </div>
        <div id="RabiesCertificate" class="row pt-4">
            <div class="col-25">
                <h2 class="text-primary; text-capitalize" align="center">RABIES VACCINATION CERTIFICATE</h2>
                <h3 class="text-primary; text-capitalize" align="center" style="font-style:italic">ADAPTED NASPHV FORM 51</h3>
                <h3 class="text-primary" align="center">Human Postexposure Rabies Treatment</h3>
                <h5 class="text-primary" align="center">@ViewData["endingparameter"]</h5>
            </div>

RabiesCert.cs:

using RabiesClinics.Data;
using RabiesClinics.Model;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;

namespace RabiesClinics.Pages.RabiesCertificates
{
    public class DetailsModel : PageModel
    {
        private readonly ApplicationDbContext _db;

        public DetailsModel(ApplicationDbContext db)
        {
            _db = db;
        }

        public IEnumerable <ViewModel> Filter { get; set; }

        public async Task OnGetAsync(string petid, DateTime vaxdate)
        {
            var petid1 = petid;
            var vaxdate1 = vaxdate.ToShortDateString();

            ViewData["endingparameter"] = $"For Pet Id: {petid1} Vaccinated On: {vaxdate1}";

            Filter = await (from x in _db.Owner
                            join y in _db.Pet on x.OwnerId equals y.OwnerId
                            join z in _db.Vaccine on x.OwnerId equals z.OwnerId
                            where y.PetId == petid && z.DateVaccinated == vaxdate
                            select new ViewModel
                            {
                                OwnerId = x.OwnerId,
                                PetId = y.PetId,
                                LastName = x.LastName,
                                FirstName = x.FirstName,
                                Telephone = x.Telephone,
                                Addr1 = x.Addr1,
                                Addr2 = x.Addr2,
                                City = x.City,
                                State = x.State,
                                Zip = x.Zip,
                                Species = y.Species,
                                Gender = y.Gender,
                                Altered = y.Altered,
                                Age = y.Age,
                                Breed = y.Breed,
                                PetName = y.PetName,
                                Colors = y.Colors,
                                DateVaccinated = (DateTime)z.DateVaccinated,
                                VaccinationExpiration = (DateTime)z.VaccinationExpiration,
                                ProducerId = z.ProducerId,
                                Duration = z.Duration,
                                VaccineLotNo = z.VaccineLotNo,
                                LotExpirationDate = (DateTime)z.LotExpirationDate,
                                VetLicenseNo = z.VetLicenseNo,
                                VetName = z.VetName,
                                VetAddress = z.VetAddress,
                            }).ToListAsync();
        }
    }
}

I've tried multiple ways to transfer the data and I can see that the button to submit next to each pet on the owner view is holding the correct owner Id and pet Id, but I must not have something correct on the receiving end on the pet view or pet.cs.

Please help and end my suffering! Thank you

UPDATES: Back to Owner Details Button on Pets Detail page:

<a asp-page="/Owners/Details" asp-route-id="ownerid1" class="btn btn-secondary" style="width: 250px;">Back to Owner Details</a>

Back to Pet Details Button on Rabies Cert page:

<a asp-page="/Pets/Details" asp-route-ownerspetid="Model.Filter.PetId" class="btn btn-link mx-2">Return to Pet Details</a>

UPDATED .cs for RABIES CERTS:

using RabiesClinics.Data;
using RabiesClinics.Model;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc;

namespace RabiesClinics.Pages.RabiesCertificates
{
    public class DetailsModel : PageModel
    {
        private readonly ApplicationDbContext _db;

        public DetailsModel(ApplicationDbContext db)
        {
            _db = db;
        }

        public IEnumerable<ViewModel> Filter { get; set; }

        public async Task OnGetAsync(string id, DateTime vaxdate)
        {
            var vaxdate1 = vaxdate.ToShortDateString();

            ViewData["endingparameter"] = $"For Pet Id: {id} Vaccinated On: {vaxdate1}";

            Filter = await (from x in _db.Owner
                            join y in _db.Pet on x.OwnerId equals y.OwnerId
                            join z in _db.Vaccine on x.OwnerId equals z.OwnerId
                            where y.PetId == id && z.DateVaccinated == vaxdate
                            select new ViewModel
                            {
                                OwnerId = x.OwnerId,
                                PetId = y.PetId,
...

Solution

  • Here’s my interpretation of this…

    Owner page:

    <a asp-page="/Pets/Details" class="btn btn-primary mx-2" 
       asp-route-ownerid="@ViewData["ownerid1"]" 
       asp-route-ownerspetid="@Model.Owner.OwnersPets[i].PetId">
        <i class="bi bi-printer"></i>
    </a>
    

    Here you are supplying the Pet page with two parameters: ownerid and ownerspetid. Now let’s look at what the Pet page does with that.

    Pet.cs:

    public void OnGet(int ownerid, string ownerspetid, string Id)
    {
        var ownerid1 = ownerid;
        var petid2 = ownerspetid;
    
        Pet = _db.Pet
            .Include(pet => pet.Vaccines).FirstOrDefault(pet => pet.PetId == Id);
    
        //Line below was working before adding ownerid and ownerspetid.
        ViewData["petid1"] = Pet.PetId;
    }
    

    It expects THREE parameters. You’re not giving it Id, but you’re using Id to fetch the pet entity. This will result in Pet being null. In the line with the error you’re trying to access a property of Pet, but it being null, you get a NullReferenceException.

    Do you really need three parameters? Is ownerspetid any different from the ID you call Id or PetId? If not, throw away ownerspetid and replace it with Id in the Owner page’s pet button. So something like this should work:

    Owner page:

    <a asp-page="/Pets/Details" class="btn btn-primary mx-2" 
       asp-route-ownerid="@ViewData["ownerid1"]" 
       asp-route-id="@Model.Owner.OwnersPets[i].PetId">
        <i class="bi bi-printer"></i>
    </a>
    

    Pet.cs:

    public async Task<IActionResult> OnGet(string id)
    {
        Pet = await _db.Pet.Include(pet => pet.Vaccines).FirstOrDefaultAsync(pet => pet.PetId == id);
    
        if (Pet is null)
            return new NotFoundResult();
    
        //Now this might work, but I don’t really see why you need it.
        //In your view you can just use @Model.Pet.PetId instead.
        ViewData["petid1"] = Pet.PetId;
        
        return Page();
    }
    

    UPDATE:

    Do you know how HTTP works/what a query string is? You pass ids through the query string like this: /pets/details?id=123 (you probably don’t need ownerid here at all!).

    To generate such a link with a query string, you use razor syntax like this:

    <a asp-page="/Pets/Details" asp-route-id="@Model.Filter.PetId" class="btn btn-link mx-2">Return to Pet Details</a>
    

    Note how they both say id. Now when you click on that link, your browser will make a GET request to /pets/details?id=123. When Asp.Net sees this request, it figures out which page to execute and what parameters to give the page’s OnGet handler. This is why your OnGet() method needs the same parameter name id. In your updated question you were still using ownerspetid.

    Let’s look at that handler’s signature again (i made it async so you can query the database asynchronously, but that’s irrelevant here):

    public async Task<IActionResult> OnGet(string id)
    

    It takes a non-nullable parameter called id, so every request to this page needs a parameter of that name.

    My advice is, forget about ViewData and store relevant data directly on your page models. Then you can access it in the razor view like @Model.Pet.PetId etc.

    A pet probably knows its owner’s id somehow (would need to see your database schema to know the details), so to create the back-button, just make sure you load it with Entity Framework and use it in a link to the owner page with asp-route-id="@Model.Pet.OwnerId" (probably).

    Since a certificate must also know its pet’s id, you can apply the same principle there.