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 code first approach for CRUD operations. After downloading the project file, restore packages and change the connection string web.config and run the update-database command in Package Manager Console.
Required Contents
- ASP.NET MVC
- JQUERY
- Sammy.JS (for Routing)
Steps
Create a new MVC project.
Add jQuery and Sammy.js reference to your layout page. Add 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");
- titleContent = $("title");
- });
-
- 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");
-
- $.get("/Student/Add", function (data) {
-
- context.$element().html(data);
- });
- });
-
- this.get("#/Student/Edit", function (context) {
- titleContent.html("Edit Student");
- $.get("/Student/Edit", {
- studentID: context.params.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");
- });
-
- 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.
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;
-
-
- 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;
-
- routingApp.runRoute('post', '#@urlPostString');
- e.preventDefault();
- e.stopPropagation();
-
- });
-
-
-
- debugger;
- if (IfLinkNotExist("POST", "#@urlPostString")) {
- routingApp.post("#@urlPostString", function (context) {
-
- var formData = new FormData($('#frmStudent')[0]);
- $.ajax({
- url: '@urlPostString',
- data: formData,
- type: "POST",
- contentType: false,
- processData: false,
- success: function (data) {
-
- 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.
Edit Student
Now, move on to Edit Module. Add Edit actions in Controller.
- public ActionResult Edit(int studentID)
- {
- 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.
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.
I hope you have enjoyed this article. Your valuable feedback, questions, or comments about this article are always welcome.