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.