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)