0
votes

Coming from ASP.NET WebForms and feeling a bit overwhelmed. I have an ASP.NET Core Razor Page that loads an incident and all the comments related to that incident. I figured out how to refresh the comments with a partial Razor Page and AJAX, but also want to add a new comment with AJAX and refresh the comments.

Models:

public class Incident
{
    public Incident()
    {
        Comments = new HashSet<Comment>();
    }

    public int Id { get; set; }

    public string Title { get; set; }

    public virtual ICollection<Comment> Comments { get; set; }
}

public class Comment
{
    public int IncidentId { get; set; }
    public int Id { get; set; }

    public string CommentText { get; set; }

    public virtual Incident Incident { get; set; }
}

Incident.cshtml:

@page
@model MyNamespace.IncidentModel

@{
    ViewData["Title"] = "Emergency Operations Center";
}

<h4>@Html.DisplayFor(model => model.Incident.Title)</h4>
@Html.HiddenFor(model => model.Incident.Id)

<hr />

<form method="post" data-ajax="true" data-ajax-method="post">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>

    <div class="row">
        <input type="hidden" asp-for="Incident.Id" />
        <div class="col form-group">
            <label asp-for="NewComment.CommentText" class="control-label"></label>
            <input asp-for="NewComment.CommentText" class="form-control" />
            <span asp-validation-for="NewComment.CommentText" class="text-danger"></span>
        </div>
        <div class="col form-group">
            <label asp-for="NewComment.EocDept" class="control-label"></label>
            <input asp-for="NewComment.EocDept" class="form-control" />
            <span asp-validation-for="NewComment.EocDept" class="text-danger"></span>
        </div>
        <div class="col-auto form-group align-self-end">
            <input type="submit" value="Add Comment" class="btn btn-primary" />
        </div>
    </div>
</form>

<button id="getComments" class="btn btn-sm btn-outline-primary"><i class="fas fa-sync-alt"></i> Refresh Comments</button>
<div id="comments" class="mt-3">
    <partial name="_CommentsPartial" model="Model.Incident.Comments.ToList()" />
</div>

<div class="mt-4">
    <a asp-page="./Index">Back to Incident list</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
    <script>
        $(function () {
            $('#getComments').on('click', function () {
                $('#comments').load('/Incident?handler=CommentsPartial&id=' + @Model.Incident.Id.ToString());
            });
        });
    </script>
}
@model List<Models.Comment>

<table class="table table-sm table-striped">
    <thead>
        <tr>
            <th>@Html.DisplayNameFor(model => model.FirstOrDefault().EocDept)</th>
            <th>@Html.DisplayNameFor(model => model.FirstOrDefault().EnterDateTime)</th>
            <th>@Html.DisplayNameFor(model => model.FirstOrDefault().EnteredBy)</th>
            <th>@Html.DisplayNameFor(model => model.FirstOrDefault().CommentText)</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>@Html.DisplayFor(modelItem => item.EocDept)</td>
                <td>@Html.DisplayFor(modelItem => item.EnterDateTime)</td>
                <td>@Html.DisplayFor(modelItem => item.EnteredBy)</td>
                <td>@Html.DisplayFor(modelItem => item.CommentText)</td>
            </tr>
        }
    </tbody>
</table>

Page model in Incident.cshtml.cs

public class IncidentModel : PageModel
{
    private readonly MyDbContext _context;

    public IncidentModel(MyDbContext context)
    {
        _context = context;
    }

    public Incident Incident { get; set; }

    [BindProperty]
    public Comment NewComment { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
            return NotFound();

        Incident = await _context.Incidents.FirstAsync(m => m.Id == id);

        if (Incident == null)
            return NotFound();

        LoadSortedComments();

        return Page();
    }

    public async Task<PartialViewResult> OnGetCommentsPartialAsync(int id)
    {
        Incident = await _context.Incidents.FirstAsync(m => m.Id == id);

        LoadSortedComments();

        return Partial("_CommentsPartial", Incident.Comments.ToList());
    }

    public void LoadSortedComments()
    {
        var entry = _context.Entry(Incident);
        entry.Collection(e => e.Comments)
            .Query()
            .OrderByDescending(c => c.EnterDateTime)
            .Load();
    }

    public async Task<IActionResult> OnPost(int id)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        // Is this the correct way to add the new Comment?
        NewComment.IncidentId = id;
        _context.Comments.Add(NewComment);
        await _context.SaveChangesAsync();

        // How do I refresh _CommentsPartial, and what gets returned here?
        return ?????;
    }
}

Questions:

  1. Is using the bound NewComment property the right way to get the new comment? (It is being added to the database, anyway.)
  2. Is getting the Incident ID from the parameter in OnPost the right way to reference the page's incident?
  3. What do I return from OnPost if I don't want to reload the entire page?
1
See stackoverflow.com/questions/46410716/… for example ajax callback to a pageBrad Patton

1 Answers

1
votes

You could load related data using Include like below;

public async Task<IActionResult> OnGetAsync(int? id)
{
        if (id == null)
            return NotFound();

        Incident = await _context.Incidents.Include(i=>i.Comments).FirstOrDefaultAsync(m => m.Id == id);

        if (Incident == null)
            return NotFound();

        return Page();
}

You could add a related entity like below

 public async Task<IActionResult> OnPost(int id)
 {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        Incident = await _context.Incidents.Include(i=>i.Comments).FirstAsync(i => i.Id == id);

        NewComment.EnterDateTime = DateTime.Now;
        Incident.Comments.Add(NewComment);
        await _context.SaveChangesAsync();
        return RedirectToPage("Incident","", new { id= Incident.Id});
  }

What do I return from OnPost if I don't want to reload the entire page?

You could add data-ajax-success in your form and add the function in like below:

<form method="post" data-ajax="true" data-ajax-method="post" data-ajax-complete="successed">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>

<div class="row">
    <input type="hidden" asp-for="Incident.Id" />
    <div class="col form-group">
        <label asp-for="NewComment.CommentText" class="control-label"></label>
        <input asp-for="NewComment.CommentText" class="form-control" />
        <span asp-validation-for="NewComment.CommentText" class="text-danger"></span>
    </div>
    <div class="col form-group">
        <label asp-for="NewComment.EocDept" class="control-label"></label>
        <input asp-for="NewComment.EocDept" class="form-control" />
        <span asp-validation-for="NewComment.EocDept" class="text-danger"></span>
    </div>
    <div class="col-auto form-group align-self-end">
        <input id="" type="submit" value="Add Comment" class="btn btn-primary" />
    </div>
</div>
</form>

@section Scripts {
  <script type="text/javascript" src="~/lib/jquery-validation-unobtrusive/jquery.unobtrusive-ajax.js"></script>
  <script>
    successed = function (result) {
        $('#comments').html(result);
    }
  </script>
}

Reference:https://www.learnrazorpages.com/razor-pages/ajax/unobtrusive-ajax