Single Page Application Using ASP.NET MVC And jQuery With CRUD methods

Introduction

In this article, I will tell you how to create a single-page application using ASP.NET MVC and jQuery. Without using Angular, React, and other third-party JavaScripts, it is difficult to achieve SPA. In this article, I will explain only controller and UI level interaction. I skipped the data access layer. If you want, please download the attachment which includes the overall code of the application.

Note. I used the code-first approach for CRUD operations. After downloading the project file, restore packages change the connection string web. config and run the update-database command in the Package Manager Console.

Required Contents

  • ASP.NET MVC
  • JQUERY
  • Sammy.JS (for Routing)

Steps

Create a new MVC project.

ASP.NET

MVC

Add jQuery and Sammy.js references to your layout page. Add a div tag (MainContent) in which we render all the partial views.

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
    <script src="~/Scripts/jquery-1.10.2.js"></script>
    <script src="~/Scripts/sammy-0.7.4.js"></script>
</head>

Create layout-routing.js file in your project which contains your application routing structure which is shown as below.

var mainContent;
var titleContent;

$(function () {
    mainContent = $("#MainContent"); // render partial views
    titleContent = $("title"); // render titles
});

var routingApp = $.sammy("#MainContent", function () {
    this.get("#/Student/Index", function (context) {
        titleContent.html("Student Page");
        $.get("/Student/Index", function (data) {
            context.$element().html(data);
        });
    });

    this.get("#/Student/Add", function (context) {
        titleContent.html("Add Student");
        //$("#BigLoader").modal('show'); // If you want to show loader
        $.get("/Student/Add", function (data) {
            //$("#BigLoader").modal('hide');
            context.$element().html(data);
        });
    });

    this.get("#/Student/Edit", function (context) {
        titleContent.html("Edit Student");
        $.get("/Student/Edit", {
            studentID: context.params.id // pass student id
        }, function (data) {
            context.$element().html(data);
        });
    });

    this.get("#/Home/About", function (context) {
        titleContent.html("About");
        $.get("/Home/About", function (data) {
            context.$element().html(data);
        });
    });

    this.get("#/Home/Contact", function (context) {
        titleContent.html("Contact");
        $.get("/Home/Contact", function (data) {
            context.$element().html(data);
        });
    });
});

$(function () {
    routingApp.run("#/Student/Index"); // default routing page
});

function IfLinkNotExist(type, path) {
    if (!(type != null && path != null))
        return false;

    var isExist = true;

    if (type.toLowerCase() == "get") {
        if (routingApp.routes.get != undefined) {
            $.map(routingApp.routes.get, function (item) {
                if (item.path.toString().replace("/#", "#").replace(/\\/g, '').replace("$/", "").indexOf(path) >= 0) {
                    isExist = false;
                }
            });
        }
    } else if (type.toLowerCase() == "post") {
        if (routingApp.routes.post != undefined) {
            $.map(routingApp.routes.post, function (item) {
                if (item.path.toString().replace("/#", "#").replace(/\\/g, '').replace("$/", "").indexOf(path) >= 0) {
                    isExist = false;
                }
            });
        }
    }
    return isExist;
}

IfLinkNotExist() check if url should not repeat, after we will add dynamic url in routing list on page load.

Add layout-routing reference in _layout.cshtml page.

<script src="~/layout-routing.js"></script>
@*@Scripts.Render("~/bundles/jquery")*@  
@Scripts.Render("~/bundles/bootstrap")  
@RenderSection("scripts", required: false)

Add a new controller ‘WelcomeController’ which has only one action ‘Index’.

namespace MvcSpaDemo.Controllers
{
    public class WelcomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
}

Create the View of ‘Index’ action using right-click.

In that View, attach the layout page link. (We need to render the layout page for the first time).

@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Welcome</h1>

Now, create student Controller which includes student CRUD operation modules. (Add, update, delete, View).

View Students

public ActionResult Index()  
{  
    return PartialView();  
}  
public ActionResult _Index()  
{  
    var students = StudentData.GetStudents();  
    return PartialView(students);  
}

Add two Views without layout page. One for outer student contents like header, add button, etc. and another for student table.

Index.cshtml

@{  
    Layout = null;  
}  
<h4>Students</h4>  
<div class="row">  
    <a href="#/Student/Add">Add Student</a>  
</div>  
<div class="row">  
    <div id="StudentDiv">  
    </div>  
</div>  
<script>  
    $(function () {  
        GetStudents();  
    });  
    function GetStudents() {  
        $.get("/Student/_Index/", function (data) {  
            $("#StudentDiv").html(data);  
        });  
    }  
    function DeleteStudent(studentID) {  
        if (confirm("Delete student?")) {  
            $.get("/Student/Delete/", { studentID: studentID }, function (data) {  
                GetStudents();  
            });  
        }  
    }  
</script>  

_Index.cshtml

@model IEnumerable<MvcSpaDemo.Entities.Student>  

@{  
    Layout = null;  
}  
<table class="table table-striped table-bordered">  
    <thead>  
        <tr>  
            <th>ID</th>  
            <th>Name</th>  
            <th>Email</th>  
            <th>Class</th>  
            <th>Action</th>  
        </tr>  
    </thead>  
    <tbody>  
        @foreach (var item in Model)  
        {  
            <tr>  
                <td>@item.StudentID</td>  
                <td>@item.FirstName @item.LastName</td>  
                <td>@item.Email</td>  
                <td>@item.Class</td>  
                <td>  
                    <a href="#/Student/[email protected]">Edit</a>  
                    <a href="javascript:;" onclick="DeleteStudent('@item.StudentID')">Delete</a>  
                </td>  
            </tr>  
        }  
    </tbody>  
</table>  

Change the default controller and action in RouteConfig.cs.

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Welcome", action = "Index", id = UrlParameter.Optional }
        );
    }
}

Run the application using F5. Do the same routing for About and Contact page also.

Contact page

Add Student asd

Now, add "Create Student Actions" in Controller.

public ActionResult Add()
{
    var student = new Student();
    ViewBag.Status = "Add";
    return PartialView(student);
}
[HttpPost]
public ActionResult Create(Student student)
{
    StudentData.AddStudent(student);
    return Json(true, JsonRequestBehavior.AllowGet);
}

We will add the add page with dynamic routing script for create or update.

@model MvcSpaDemo.Entities.Student

@{
    ViewBag.Title = "Add";
    Layout = null;

    // We use same page for add and edit.
    var urlPostString = "/Student/Create";
    if (ViewBag.Status == "Edit")
    {
        urlPostString = "/Student/Update";
    }
}
<h2>@ViewBag.Status Student</h2>

<form id="frmStudent" name="frmStudent" method="post" action="#@urlPostString">
    @Html.HiddenFor(x => x.StudentID)
    <div class="row">
        <div class="form-group">
            <label for="Variables">First Name</label>
            @Html.TextBoxFor(x => x.FirstName, new { @class = "form-control square" })
        </div>
        <div class="form-group">
            <label for="Variables">Last Name</label>
            @Html.TextBoxFor(x => x.LastName, new { @class = "form-control square" })
        </div>
        <div class="form-group">
            <label for="Variables">Email</label>
            @Html.TextBoxFor(x => x.Email, new { @class = "form-control square" })
        </div>
        <div class="form-group">
            <label for="Variables">Class</label>
            @Html.TextBoxFor(x => x.Class, new { @class = "form-control square" })
        </div>
        <div class="form-group">
            <input type="submit" class="btn btn-primary" value="Submit" />
        </div>
    </div>
</form>

<script>
    $("#frmStudent").on("submit", function (e) {
        debugger;
        // if ($("#frmStudent").valid()) {
        routingApp.runRoute('post', '#@urlPostString');
        e.preventDefault();
        e.stopPropagation();
        // }
    });

    // add dynamic create or update link
    debugger;
    if (IfLinkNotExist("POST", "#@urlPostString")) {
        routingApp.post("#@urlPostString", function (context) {
            // $("#BigLoader").modal('show');
            var formData = new FormData($('#frmStudent')[0]);
            $.ajax({
                url: '@urlPostString',
                data: formData,
                type: "POST",
                contentType: false,
                processData: false,
                success: function (data) {
                    // $("#BigLoader").modal('hide');
                    if (data) {
                        if ('@ViewBag.Status' == 'Add')
                            alert("Student successfully added");
                        else if ('@ViewBag.Status' == 'Edit')
                            alert("Student successfully updated");
                        window.location.href = "#/Student/Index";
                    } else {
                        alert('Something went wrong');
                    }
                },
                error: function () {
                    alert('Something went wrong');
                }
            });
        });
    }
</script>

Now, run the application.

Application

OK

Localhost

Edit Student

Now, move on to Edit Module. Add Edit actions in Controller.

public ActionResult Edit(int studentID) // studentID nothing but parameter name which we pass in layout-routing.
{
    var student = StudentData.GetStudentById(studentID);
    ViewBag.Status = "Edit";
    return PartialView("Add", student);
}
[HttpPost]
public ActionResult Update(Student student)
{
    StudentData.UpdateStudent(student);
    return Json(true, JsonRequestBehavior.AllowGet);
}

We used the same partial view for add and edit, so there is no need to create a new View for edit.

After adding the action methods, just run the application.

Edit Student

Contact

List

Delete Student

Now, we will implement the Delete operation.

We have already written Delete button's code in Student Index.cshtml.

function GetStudents() {
    $.get("/Student/_Index/", function (data) {
        $("#StudentDiv").html(data);
    });
}

function DeleteStudent(studentID) {
    if (confirm("Delete student?")) {
        $.get("/Student/Delete/", { studentID: studentID }, function (data) {
            GetStudents();
        });
    }
}

Run the application.

Run application

Add Student

I hope you have enjoyed this article. Your valuable feedback, questions, or comments about this article are always welcome.