Use Ajax to perform CRUD operations in ASP.NET Core Razor Pages
ASP.NET Core Razor Pages offer a simple page based programming model for the
developers. A razor pages consists of a .cshtml files and associated page model
class. The page model class can house actions to handle HTTP verbs. For example,
a page model class can contain OnGet(), OnPost(), OnPut(), and OnDelete()
actions to deal with GET, POST, PUT, and DELETE verbs respectively. This sounds
straightforward as far as requests are initiated by a form. However, things are
bit tricky while making Ajax calls to a razor page. To that end this article
illustrates how to develop a razor page that performs CRUD operations using Ajax
calls.
Look at the following razor page - Index.cshtml - that shows how our sample
is going to be :
There is a dropdown at the top that lists all the existing CustomerIDs from
the Northwind database. Upon selecting a CustomerID its details such as
CompanyName, ContactName, and Country are displayed in the respective textboxes.
Clicking Insert, Update, and Delete buttons initiate Ajax calls to the server
and attempt to perform the corresponding operations.
Ok. Let's get going.
Creating DbContext and model
Create a new web application in ASP.NET Core using Razor Pages template
(default). Then replace the existing Index.cshtml file with a new blank one. If
you wish you can clean the existing Index.file and use that.
Then add Models folder and add Customer class to it. The following code shows
how the Customer class looks like :
[Table("Customers")]
public class Customer
{
[Key]
public string CustomerID { get; set; }
public string CompanyName { get; set; }
public string ContactName { get; set; }
public string Country { get; set; }
}
The Customer class maps to the Customers table of Northwind database and
contains four properties - CustomrID, CompanyName, ContactName, and Country.
Then add NorthwindDbContext class and write the following code into it :
public class NorthwindDbContext:DbContext
{
public NorthwindDbContext
(DbContextOptions<NorthwindDbContext> options) :
base(options)
{
}
public DbSet<Customer> Customers { get; set; }
}
The NorthwindDbContext class contains Customers DbSet and a constructor
needed for DI registration.
Now open Startup class and modify ConfigureServices() method as shown below :
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddEntityFrameworkSqlServer();
services.AddDbContext<NorthwindDbContext>
(options => options.UseSqlServer("data
source=.;initial catalog=Northwind;
integrated security=true"));
services.AddAntiforgery(options =>
options.HeaderName = "MY-XSRF-TOKEN");
}
The ConfigureServices() method registers services with the DI container.
Notice the calls marked in bold letters. The AddDbContext() registers
NorthwindDbContext with the DI container. Make sure to change the database
connection string as per your setup. The AddAntiforgery() method configures
anti-forgery service to pick the anti-forgery token from request headers rather
than request body. This is required because we will be issuing Ajax requests to
the razor page and there won't be any full page post-backs.
Add jQuery library files and Index.cshtml markup
This completes the Startup configuration. Now, add Scripts subfolder under
wwwroot filder and place jQuery library files into it.
Then open Index.cshtml file and add the following HTML markup into it. This
mark renders the UI as shown at the beginning of this article.
@page "{id?}"
@model AjaxRazorPages.Pages.IndexModel
<script src="~/Scripts/jquery-3.2.1.js"></script>
<h1>Manage Customers</h1>
<form>
@Html.AntiForgeryToken()
<table border="1" cellpadding="10">
<tr>
<td>Customer ID :</td>
<td>
<select id="customerid"></select>
OR
<input id="newcustomerid" type="text" />
</td>
</tr>
<tr>
<td>Company Name :</td>
<td><input id="companyname" type="text" /></td>
</tr>
<tr>
<td>Contact Name :</td>
<td><input id="contactname" type="text" /></td>
</tr>
<tr>
<td>Country :</td>
<td><input id="country" type="text" /></td>
</tr>
<tr>
<td colspan="2">
<input type="button" id="insert"
value="Insert" />
<input type="button" id="update"
value="Update" />
<input type="button" id="delete"
value="Delete" />
</td>
</tr>
</table>
<br />
<div id="msg"></div>
</form>
Most of the markup is quite straightforward. Notice the markup shown in bold
letters.
The page route is configured to receive an optional id parameter. For select
by ID, update and delete operations we need to pass this ID to the server.
We emit an anti-forgery token using Html.AntiForgeryToken() helper. Note that
the form tag helper automatically emits an anti-forgery token if its method
attribute is set to post. In this case we will be making Ajax requests to the
server and hence form tag doesn't have method attribute.
The three input elements representing the three buttons have type attribute
set to button rather than submit. This is because we want to make Ajax calls
rather than full page post-backs. Finally, the msg <div> element is used to
display the message returned from the server after performing the CRUD
operations.
Create actions to perform CRUD operations
In my recent article available
here, I have already
explained how multiple razor pages actions can be created. We need to follow a
similar approach here. The difference, however, is that these actions will be
invoked from jQuery Ajax code.
Go to the page model class of Index.cshtml - IndexPageModel - and write the
following actions to it :
public class IndexModel : PageModel
{
private NorthwindDbContext db;
public IndexModel(NorthwindDbContext db)
{
this.db = db;
}
public void OnGet()
{
}
public IActionResult OnGetSelectAll()
{
List<Customer> data = db.Customers.ToList();
return new JsonResult(data);
}
public IActionResult OnGetSelectByID(string id)
{
Customer data = db.Customers.Find(id);
return new JsonResult(data);
}
public IActionResult OnPostInsert
([FromBody]Customer obj)
{
db.Customers.Add(obj);
db.SaveChanges();
return new JsonResult
("Customer Added Successfully!");
}
public IActionResult OnPutUpdate
(string id,[FromBody]Customer obj)
{
db.Customers.Update(obj);
db.SaveChanges();
return new JsonResult
("Customer Modified Successfully!");
}
public IActionResult OnDeleteDelete(string id)
{
db.Customers.Remove(db.Customers.Find(id));
db.SaveChanges();
return new JsonResult
("Customer Deleted Successfully!");
}
}
The above code consists of the constructor and five actions that perform CRUD
operations. The constructor receives the injected DbContext and stores it in a
local variable. Note that the methods doing CRID return IActionResult to the
caller. This way we can return data to the Ajax calls using JsonResult() method
of the controller base class.
The five methods that perform the CRUD are explained below :
- OnGetSelectAll() : Returns a list of all Customer objects.
- OnGetSelectByID() : Receives a CustoemrID that is to be returned to the
caller and returns it accordingly.
- OnPostInsert() : Receives a Customer object and attempts to INSERT it to
the database. The Customer parameter is model bound with the request body
using [FromBody] attribute.
- OnPutUpdate() : Receives a CustomerID and modified Customer object and
attempts to UPDATE the customer in the database. The Customer parameter is
model bound with the request body using [FromBody] attribute.
- OnDeleteDelete() : Receives a CustomerID and attempts to DELETE a
customer from the database.
I won't go into the details of the EF Core code here since it's quite
straightforward and chances are you will already be familiar with it.
Now that we have written the actions for performing CRUD operations, it's
time to write the jQuery code that invokes these actions.
Filling the dropdown list with CustomerIDs
$(document).ready(function () {
var options = {};
options.url = "/Index?handler=SelectAll";
options.type = "GET";
options.dataType = "json";
options.success = function (data) {
data.forEach(function (element) {
$("#customerid").append("<option>"
+ element.customerID + "</option>");
});
};
options.error = function () {
$("#msg").html("Error while
making Ajax call!");
};
$.ajax(options);
});
The above code makes an Ajax request using $.ajax() method of jQuery. Notice
how the url, type and dataType properties of the options object are specified.
Since we wish to invoke OnGetSelectAll() action, the url points to Index page.
The URL also mentions the handler query string parameter with the name of the
action to be invoked. Notice how the action name is specified - to invoke
OnGetSelectAll() we specify handler as SelectAll. This is how razor pages know
which action to invoke.
The HTTP verb used is GET and the response data type is set to json. The
success function simply fills the dropdown list with a series of element each
wrapping a CustoemrID. The error function displays an error message in case
something goes wrong while calling the action.
Displaying details of a selected customer
The change event handler of the dropdown looks like this:
$("#customerid").change(function () {
var options = {};
options.url = "/Index/" +
$("#customerid").val() + "?handler=SelectByID";
options.type = "GET";
options.dataType = "json";
options.success = function (data) {
$("#companyname").val(data.companyName);
$("#contactname").val(data.contactName);
$("#country").val(data.country);
};
options.error = function () {
$("#msg").html("Error while making Ajax call!");
};
$.ajax(options);
});
This code is quite similar to the previous one. However, it appends the
CustomerID whose details are to be fetched to the url. Notice that handler query
string parameter is set to SelecyByID.
The success function fills the three textboxes with CompanyName, ContactName
and Country. Notice something important - the property names are automatically
converted to use camel casing. This way client side code gets to stick with the
JavaScript ways of naming the things whereas server side code can continue to
stick to the C# ways of naming the things.
Adding a new customer
The click event handler of the Insert button is shown below:
$("#insert").click(function () {
var options = {};
options.url = "/Index?handler=Insert";
options.type = "POST";
var obj = {};
obj.customerID = $("#newcustomerid").val();
obj.companyName = $("#companyname").val();
obj.contactName = $("#contactname").val();
obj.country = $("#country").val();
options.data = JSON.stringify(obj);
options.contentType = "application/json";
options.dataType = "json";
options.beforeSend = function (xhr) {
xhr.setRequestHeader("MY-XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
};
options.success = function (msg) {
$("#msg").html(msg);
};
options.error = function () {
$("#msg").html("Error while making Ajax call!");
};
$.ajax(options);
});
The above code uses POST verb to make the Ajax call. It also sets the handler
query string parameter to Insert. Moreover, it sets data, dataType and
contentType properties. The data property is set to the stringified version of
the new customer object. Notice that this new object also uses camel casing
while setting the properties. The contentType property indicates the request's
data type - JSON in this case.
Here something more important is also happening. Notice the beforeSend
function. The beforeSend function is called before making the Ajax request. It
adds a request header - XSRF-TOKEN - that gets its value from
the anti-forgery token stored in a hidden form field. This token gets created
due to the @Html.AntiForgeryToken() call we have placed in the <form>.
Unlink MVC, razor pages automatically perform the anti-forgery check for you.
In the standard post-back scenarios the token is emitted by the form tag helper
automatically. The token is automatically validated when you submit the form. In
this example we are issuing Ajax request and hence we need to emit and submit
the token programmatically as shown above.
The success function simply displays the message returned by the Post()
action into the msg <div> element.
Modifying an existing customer
The click event of the Update button is shown below:
$("#update").click(function () {
var options = {};
options.url = "/Index/"
+ $("#customerid").val() + "?handler=Update";
options.type = "PUT";
var obj = {};
obj.customerID = $("#customerid").val();
obj.companyName = $("#companyname").val();
obj.contactName = $("#contactname").val();
obj.country = $("#country").val();
options.data = JSON.stringify(obj);
options.contentType = "application/json";
options.dataType = "html";
options.beforeSend = function (xhr) {
xhr.setRequestHeader("MY-XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
};
options.success = function (msg) {
$("#msg").html(msg);
};
options.error = function () {
$("#msg").html("Error while making Ajax call!");
};
$.ajax(options);
});
Most of the above code is similar to the code you wrote in the insert click
event handler. The CustomerID being modified is appended to the url. The handler
query string parameter is now set to Update. The HTTP verb is set to PUT. As
mentioned earlier this Ajax call also requires the anti-forgery token and hence
beforeSend is wired to set the request header.
Deleting a customer
Finally, the code that deletes a customer is shown below:
$("#delete").click(function () {
var options = {};
options.url = "/Index/"
+ $("#customerid").val() + "?handler=Delete";
options.type = "DELETE";
options.dataType = "html";
options.beforeSend = function (xhr) {
xhr.setRequestHeader("MY-XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
};
options.success = function (msg) {
$("#msg").html(msg);
};
options.error = function () {
$("#msg").html("Error while making Ajax call!");
};
$.ajax(options);
});
The above code sets the HTTP verb to DELETE and makes an Ajax call as before.
This completes the jQuery code. Run the Index page and check whether all the
actions are working as expected.
That's it for now! Keep coding !!