ASP.NET Core 2.2 - Details Modal With JSON Entity
This article will demonstrate a call to a page handler which returns entity details in JSON. The details are displayed on a Bootstrap modal. I will assume you have created a new ASP.NET Core 2.2 Razor Pages project. I won't use Identity or Individual User Accounts.
This article continues a series about table functions:
The FREE ASP.NET Core 6.0 - Demos And Utilities Project includes a Table Functions Demo. Access to the source code is free for registered users at Manage > Assets.
If we create a page handler, we can use a Bootstrap modal rather than redirect to a page.
Edit Index.cshtml.cs > IndexModel, add the page handler:
public async Task<JsonResult> OnGetDetailsByIdAsync(int id = 0)
{
    if (id == 0)
    {
        return new JsonResult(new { Status = "Failed", Errors = "id = 0" });
    }
    var foodDetails = await _context.Foods
        .Where(f => f.Id == id)
        .Select(f => new Food
        {
            Id = f.Id,
            Name = f.Name,
            FoodType = f.FoodType,
            ColdStore = f.ColdStore,
            Date = f.Date
        })
        .FirstOrDefaultAsync();
    if (foodDetails == null)
    {
        return new JsonResult(new { Status = "Failed", Errors = "Details Not Found" });
    }
    return new JsonResult(new { Status = "Success", foodDetails });
}
    Edit Index.cshtml, add the modal:
<div class="modal">
    <div class="modal-dialog">
        <div class="modal-content">
            <!-- Modal Header -->
            <div class="modal-header">
                <h4 class="modal-title">Food</h4>
                <button type="button" class="close" data-dismiss="modal">×</button>
            </div>
            <!-- Modal body -->
            <div class="modal-body">
            </div>
            <!-- Modal footer -->
            <div class="modal-footer">
                <button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
            </div>
        </div>
    </div>
</div>
    Edit Index.cshtml, update the details link to a modal button:
<button type="button" class="btn btn-link p-0 details" data-id="@item.Id"
    data-toggle="modal" data-target=".modal">Details</button>
    Edit Index.cshtml, add the JavaScript:
@section scripts {
        <script>
        const modalBody = document.querySelector('.modal-body');
        function getDetailsById(id) {
            return fetch('/index?handler=DetailsById&id=' + id,
                {
                    method: 'get',
                    headers: {
                        'Content-Type': 'application/json;charset=UTF-8'
                    }
                })
                .then(function (response) {
                    if (response.ok) {
                        return response.text();
                    } else {
                        throw Error('DetailsById Response Not OK');
                    }
                })
                .then(function (text) {
                    try {
                        return JSON.parse(text);
                    } catch (err) {
                        throw Error('DetailsById Method Not Found');
                    }
                })
                .then(function (responseJSON) {
                    modalBody.innerHTML = '';
                    if (responseJSON.status === 'Success') {
                        var foodDetails = responseJSON.foodDetails;
                        var dl = document.createElement('dl');
                        for (prop in foodDetails) {
                            var dt = document.createElement('dt');
                            dt.textContent = prop;
                            dl.appendChild(dt);
                            var dd = document.createElement('dd');
                            dd.textContent = foodDetails[prop].length === 0 ? 'empty' : foodDetails[prop];
                            dl.appendChild(dd);
                        }
                        var successDiv = document.createElement('div');
                        successDiv.classList.add('alert', 'alert-success');
                        successDiv.appendChild(dl);
                        modalBody.appendChild(successDiv);
                    }
                    else {
                        var dl = document.createElement('dl');
                        for (prop in responseJSON) {
                            var dt = document.createElement('dt');
                            dt.textContent = prop;
                            dl.appendChild(dt);
                            var dd = document.createElement('dd');
                            dd.textContent = responseJSON[prop];
                            dl.appendChild(dd);
                        }
                        var alertDiv = document.createElement('div');
                        alertDiv.classList.add('alert', 'alert-danger');
                        alertDiv.appendChild(dl);
                        modalBody.appendChild(alertDiv);
                    }
                })
                .catch(function (error) {
                    modalBody.innerHTML = '';
                    var alertDiv = document.createElement('div');
                    alertDiv.classList.add('alert', 'alert-danger');
                    alertDiv.textContent = 'Error ' + error;
                    modalBody.appendChild(alertDiv);
                });
        }
        // Wait for the page to load first
        document.addEventListener('DOMContentLoaded', function () {
            //Get a reference to the links on the page
            var details = document.querySelectorAll('.details');
            var dl = details.length;
            for (i = 0; i < dl; i++) {
                details[i].addEventListener('click', function (event) {
                    modalBody.innerHTML = '';
                    var spinnerDiv = document.createElement('div');
                    spinnerDiv.classList.add('spinner-border');
                    spinnerDiv.classList.add('text-alert');
                    spinnerDiv.classList.add('d-flex');
                    spinnerDiv.classList.add('mt-2');
                    spinnerDiv.classList.add('ml-auto');
                    spinnerDiv.classList.add('mr-auto');
                    spinnerDiv.setAttribute('role', 'status');
                    var span = document.createElement('span');
                    span.classList.add('sr-only');
                    span.innerText = 'Loading...';
                    spinnerDiv.appendChild(span);
                    modalBody.appendChild(spinnerDiv);
                    getDetailsById(event.target.dataset.id);
                    return false;
                });
            }
        });
    </script>
}
    Build, run and test.
The Food class DataAnotations do not get serialized. Let's add a JSON Food model with a Status property.
Edit Entities > Food.cs, add a FoodJsonModel:
public class FoodJsonModel
{
    [JsonProperty(PropertyName = "Status")]
    public string Status { get; set; }
    [JsonProperty(PropertyName = "Id")]
    public int Id { get; set; }
    [JsonProperty(PropertyName = "Name")]
    public string Name { get; set; }
    [JsonProperty(PropertyName = "Food Type")]
    public string FoodType { get; set; }
    [JsonProperty(PropertyName = "Store Cold")]
    public bool ColdStore { get; set; }
    [JsonProperty(PropertyName = "Date")]
    public string Date { get; set; }
}
    Edit Index.cshtml.cs > IndexModel, update the page handler:
public async Task<JsonResult> OnGetDetailsByIdAsync(int id = 0)
{
    if (id == 0)
    {
        return new JsonResult(new { Status = "Failed", Errors = "id = 0" });
    }
    var foodDetails = await _context.Foods
        .Where(f => f.Id == id)
        .Select(f => new FoodJsonModel
        {
            Status = "Success",
            Id = f.Id,
            Name = f.Name,
            FoodType = f.FoodType.ToString(),
            ColdStore = f.ColdStore,
            Date = string.Format("{0:MM/dd/yyyy hh:mm tt}", f.Date)
        })
        .FirstOrDefaultAsync();
    if (foodDetails == null)
    {
        return new JsonResult(new { Status = "Failed", Errors = "Details Not Found" });
    }
    return new JsonResult(foodDetails);
}
    Edit Index.cshtml, update the JavaScript:
        function getDetailsById(id) {
            return fetch('/index?handler=DetailsById&id=' + id,
                {
                    method: 'get',
                    headers: {
                        'Content-Type': 'application/json;charset=UTF-8'
                    }
                })
                .then(function (response) {
                    if (response.ok) {
                        return response.text();
                    } else {
                        throw Error('DetailsById Response Not OK');
                    }
                })
                .then(function (text) {
                    try {
                        return JSON.parse(text);
                    } catch (err) {
                        throw Error('DetailsById Method Not Found');
                    }
                })
                .then(function (responseJSON) {
                    var dl, dt, dd;
                    modalBody.innerHTML = '';
                    if (responseJSON.Status === 'Success') {
                        dl = document.createElement('dl');
                        for (prop in responseJSON) {
                            dt = document.createElement('dt');
                            dt.textContent = prop;
                            dl.appendChild(dt);
                            dd = document.createElement('dd');
                            dd.textContent = responseJSON[prop].length === 0 ? 'empty' : responseJSON[prop];
                            dl.appendChild(dd);
                        }
                        var successDiv = document.createElement('div');
                        successDiv.classList.add('alert', 'alert-success');
                        successDiv.appendChild(dl);
                        modalBody.appendChild(successDiv);
                    }
                    else {
                        dl = document.createElement('dl');
                        for (prop in responseJSON) {
                            dt = document.createElement('dt');
                            dt.textContent = prop;
                            dl.appendChild(dt);
                            dd = document.createElement('dd');
                            dd.textContent = responseJSON[prop];
                            dl.appendChild(dd);
                        }
                        var alertDiv = document.createElement('div');
                        alertDiv.classList.add('alert', 'alert-danger');
                        alertDiv.appendChild(dl);
                        modalBody.appendChild(alertDiv);
                    }
                })
                .catch(function (error) {
                    modalBody.innerHTML = '';
                    var alertDiv = document.createElement('div');
                    alertDiv.classList.add('alert', 'alert-danger');
                    alertDiv.textContent = 'Error ' + error;
                    modalBody.appendChild(alertDiv);
                })
        }
    Build, run and test.
Update 12/30/2021
Announced the ASP.NET Core 6.0 - Demos And Utilities Project. Updated articles, asset and topic links.
Comments(0)