Tap the power of breath, mantra, mudra, and dhyana.
Online course in Advanced Ajapa Japa and Shambhavi Mudra Meditation by Bipin Joshi.


CRUD using fetch() and ASP.NET Core MVC

In the previous article we completed the Razor Pages example that uses fetch() to perform CRUD operations. You can use the same technique to call ASP.NET Core MVC controller actions. In this article we will migrate our Razor Pages code to MVC.

Let's get going.

Open the same project you created in the previous article. And add Controllers and Views/Home folders as you normally do for any MVC project.

Then add HomeControlller.cs and Index.cshtml to the Controllers and Views/Home folders respectively. Your Solution Explorer should resemble the following figure:

In the Razor Pages example, whatever went inside the page model class will now go into the HomeController class.

So, first we inject the AppDbContext into the constructor as shown below:

private readonly AppDbContext db;

public HomeController(AppDbContext db)
{
    this.db = db;
}

I won't go into the details of this code because we have already discussed it in the Razor Pages example.

The following code shows the Index() action that returns the Index view containing the UI.

public IActionResult Index()
{
    return View();
}    

We then add a couple of actions that take care of SELECTing Customer data and returning its JSON representation to the caller. They are shown below:

public IActionResult SelectAll()
{
    List>Customer< data = db.Customers.ToList();
    return new JsonResult(data);
}

public IActionResult SelectByID(string id)
{
    Customer data = db.Customers.Find(id);
    return new JsonResult(data);
}    

As you can see, the SelectAll() action returns a List of all the customers to the caller whereas the SelectByID() action returns only that Customer whose CustomerID is passed in the route. Notice that results are wrapped in the JsonResult object so that the client code receives JSON data.

Then we add Insert(), Update(), and Delete() actions that take care of the respective operations.

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Insert([FromBody] 
Customer obj)
{
    db.Customers.Add(obj);
    db.SaveChanges();
    return new JsonResult
    ("Customer Added Successfully!");
}

[HttpPut]
[ValidateAntiForgeryToken]
public IActionResult Update(string id, 
[FromBody] Customer obj)
{
    db.Customers.Update(obj);
    db.SaveChanges();
    return new JsonResult
    ("Customer Modified Successfully!");
}

[HttpDelete]
[ValidateAntiForgeryToken]
public IActionResult Delete(string id)
{
    db.Customers.Remove(db.Customers.Find(id));
    db.SaveChanges();
    return new JsonResult
    ("Customer Deleted Successfully!");
}

This code should look familiar to you because we used something similar in the Razor Pages example. The main difference between the Razor Pages code and the above MVC code is -- this code was a part of page handlers in Razor Pages whereas it takes the form of actions in MVC. Moreover, various actions are decorated with HTTP verb specific attributes such as [HttpPost], [HttpPut], and [HttpDelete].

This completes the HomeController. Now open the Index view and add the following markup in it.

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>CRUD using fetch() in Razor Pages </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="message"></div>

</form>

This markup is almost identical to the Razor Pages markup except that it doesn't have the @page and @model directives at the top.

The JavaScript code that uses fetch() calls to invoke the MVC actions is quite similar to the Razor Pages example. However, instead of specifying page handler name in the query string, it now points to a particular MVC action.

Consider the following code that fills the Customers dropdown list.

document.addEventListener("DOMContentLoaded", 
async function () {

    var customerid = document.getElementById
    ("customerid");
    var newcustomerid = document.getElementById
    ("newcustomerid");
    var companyname = document.getElementById
    ("companyname");
    var contactname = document.getElementById
    ("contactname");
    var country = document.getElementById
    ("country");

    var insert = document.getElementById("insert");
    var update = document.getElementById("update");
    var del = document.getElementById("delete");

    var message = document.getElementById("message");

    message.innerHTML = "";

    
    const request = new Request("/Home/SelectAll");
    

    const options = {
        method:"GET"
    };

    const response = await fetch(request,options);

    if (!response.ok) {
        message.innerHTML = `<h2>${response.status} 
            - ${response.statusText}</h2>`;
        return;
    }
    const json = await response.json();

    json.forEach(function(customer){
        const option = document.createElement('option');
        option.value = customer.customerID;
        option.innerHTML = customer.customerID;
        customerid.appendChild(option);
    });

    message.innerHTML = "Customers fetched successfully.";
});

Notice the code shown in the bold letter. We specify the request URL to be /Home/SelectAll. Rest of the code is identical to the Razor Pages example.

You need to make this URL change everywhere in the JavaScript code keeping the rest of the code intact.

The change event handler is given below:

customerid.addEventListener('change', async function(){
    
    const request = new Request
    ("/Home/SelectByID/"+ customerid.value);
    
    const options = {
        method: "GET"
    };

    const response = await fetch(request, options);

    if (!response.ok) {
        message.innerHTML = `<h2>${response.status} 
            - ${response.statusText}</h2>`;
        return;
    }
    const json = await response.json();
    companyname.value = json.companyName;
    contactname.value = json.contactName;
    country.value = json.country;
});

As you can see, this time we point the request URL to /Home/SelectByID/customerid.

The click event handler of the insert button is shown below:

insert.addEventListener('click', async function(){
    
    const request = new Request("/Home/Insert");
    
    let customer = {};
    customer.customerID = newcustomerid.value;
    customer.companyName = companyname.value;
    customer.contactName = contactname.value;
    customer.country = country.value;

    const token = document.getElementsByName
    ("__RequestVerificationToken")[0];

    const options = {
        method: "POST",
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
            'MY-XSRF-TOKEN': token.value
        },
        body: JSON.stringify(customer)
    };
    const response = await fetch(request, options);

    if (!response.ok) {
        message.innerHTML = `<h2>${response.status} 
            - ${response.statusText}</h2>`;
        return;
    }
    const msg = await response.json();
    message.innerHTML = msg;
});

The above code points to /Home/Insert action. The update click handler is as follows:

update.addEventListener('click', async function(){
    
    
    const request = new Request("/Home/Update/" + 
    customerid.value);
    

    let customer = {};
    customer.customerID = customerid.value;
    customer.companyName = companyname.value;
    customer.contactName = contactname.value;
    customer.country = country.value;

    const token = document.getElementsByName
    ("__RequestVerificationToken")[0];

    const options = {
        method: "PUT",
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
            'MY-XSRF-TOKEN': token.value
        },
        body: JSON.stringify(customer)
    };

    const response = await fetch(request, options);

    if (!response.ok) {
        message.innerHTML = `<h2>${response.status} 
            - ${response.statusText}</h2>`;
        return;
    }
    const msg = await response.json();
    message.innerHTML = msg;
});

We now point to the /Home/Update action and also pass the CustomerID in the route parameter. Finally, the delete click handler is shown below:

del.addEventListener('click', async function () {
    
    const request = new Request("/Home/Delete/" + 
    customerid.value);
    
    const token = document.getElementsByName
    ("__RequestVerificationToken")[0];
    const options = {
        method: "DELETE",
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
            'MY-XSRF-TOKEN': token.value
        }
    };
    const response = await fetch(request, options);
    if (!response.ok) {
        message.innerHTML = `<h2>${response.status} 
            - ${response.statusText}</h2>`;
        return;
    }
    const msg = await response.json();
    message.innerHTML = msg;

    // refill the dropdown list

    const request2 = new Request
    ("/Index?handler=SelectAll");
    const options2 = {
        method: "GET"
    };
    const response2 = await fetch(request2, options2);
    if (!response2.ok) {
        message.innerHTML = `<h2>${response2.status} 
            - ${response2.statusText}</h2>`;
        return;
    }
    const json2 = await response2.json();
    customerid.innerHTML = "";
    json2.forEach(function (customer) {
        const option = document.createElement('option');
        option.value = customer.customerID;
        option.innerHTML = customer.customerID;
        customerid.appendChild(option);
    });
});

This time the request URL points to the Home/Delete action and also contains a CustomerID to be deleted in the route.

This completes the MVC application. Run the project and naviagate to /Home/Index. You should see this UI with a list of customers filled in the dropdown list.

In the next part of this series we will call a Web API from the fetch() calls.

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 : 13 March 2023