Advanced Ajapa Yoga Kriyas and Meditations for Software Developers : Tap the power of breath, mantra, mudra, and dhyana for improved focus, peace of mind, and blissful inner connection.


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 !!


Bipin Joshi is an independent software consultant and trainer by profession specializing in Microsoft web development technologies. Having embraced the Yoga way of life he is also a meditation teacher and spiritual guide to his students. He is a prolific author and writes regularly about software development and yoga on his websites. He is programming, meditating, writing, and teaching for over 27 years. To know more about his ASP.NET online courses go here. More details about his Ajapa Japa and Shambhavi Mudra online course are available here.

Posted On : 05 February 2018







Advanced Ajapa Yoga Kriyas and Meditations for Software Developers : Tap the power of breath, mantra, mudra, and dhyana for improved focus, peace of mind, and blissful inner connection.