Learning ASP.NET Core 3.1 MVC from Scratch
CRUD and Search operations in ASP.NET Core 3.1 MVC from scratch.
The Application:
Here is how the finished product will look like and work:
The web application we’re building is an employee database where HR can add information on employees. To achieve this we’ll cover all the basic concepts like Viewing records, editing, adding, searching, and deleting records. All of this will be done using simple straightforward ASP.NET Core MVC version 5 and the only perquisites this article requires is an understanding of what object-oriented programming is and basic SQL. Languages that you’ll work within this article are:
- C#
- TypeScript
- CSS/Bootstrap
- cshtml
It’s a long tutorial because I’ve taken the approach which will enable you to learn how real-world web applications are build and not just the printing Hello World type concept. The database used is storing data locally but t is accessed in the same way I access the database with a live web application on the cloud. So, you are learning it from scratch but on professional level principles. You’ll also learn how to install/include external libraries and how to use typescript code in Views and so many other things. People who have a background in other programming languages are a plus for this tutorial and it is Windows focused so if you’re learning it on Mac this tutorial is not for you yet.
Typos and grammatical mistakes are expected in this long article. Do add a private note if you find any and I’ll fix them.
Downloads:
Download SQL Server Management Studio (SSMS). It’s free!
After downloading install it and connect it to see if it’s working. The image below show some information, it might be different information for you but if you choose Database Engine and type in localhost as server name and select Windows Authentication it connected for me.
If you see the screen below that means your SQL server is connected and working.
Next download Visual Studio 2019 Community Edition which is also free:
After downloading, install it and run it.
That’s it for the required downloads and installations.
Create a new project:
Open Visual Studio and Click on Create a new project
Select ASP.NET Core Web Application.
Give your project a name i.e. IBJOffice and give your project a location where it’ll be saved and click Create.
Lastly, select Web Application (Model-View-Controller) and make sure other settings are the same as shown in the image below:
And you’ve successfully created a new project. Go ahead and explore by expanding the Solutions Explorer on the right side to see what type of files are in it.
Let’s Begin Coding:
We’ll be using some additional packages which are required for making database connections, migrations and so much more. I’ll try to mention in the code that this was made possible because of that library but let’s install the libraries first so that when we get this out of the way we can code smoothly.
To install packages in .NET select TOOLS >NuGet Package Manager > Manage NuGet Packages or solutions
On Browse enter the Microsoft.AspNetCore.MVC.Razor.RuntimeCompilation in the search bar and select the result. You need to click on install on the left-right screen after selecting the project.
This package will allow you to view HTML/CSS based changes just by refreshing the browser and not running the project again from point start. A pretty helpful package which is recommended but not required for this project. The whole point is to make you familiar with how to install packages.
Just like you installed the package above install these other packages below:
Now that the packages are installed let’s code.
Let’s make some changes on the Views/Home/Index.cshtml
page and then run the project.
This is what you’ll see in the index.cshtml
. This page will run if you run the project at this point as this is the home page created by Visual Studio when you created the new project. The @{...}
on the top is basically c-sharp (C#) syntax and this is why it’s .cshtml
because you can embed C# and HTML code together. Let’s make some changes to the HTML part of the Index.cshtml
The changes that you’ve just made are plain HTML and using Bootstrap styling. Bootstrap is builtin in Visual Studio. For example, jumbotron is a class in bootstrap that’ll give you a grayish background. The same goes with col-md-4
will basically select 4 columns from 12 column layout of bootstrap. You can Learn more about bootstrap as I won’t be going into details but just wanted to give you a flavor of where bootstrap is in the code so that in the upcoming code you’ll know where some of the weird class names are coming from from.
Go ahead run the project by pressing F5
or from the menu select DEBUG > Start Debugging or by clicking the play butt ISS Express in the tools bar.
Let’s make a change in the header and footer of the page above. The header and footer will be common on all pages, meaning if you click on privacy you’ll see that the page is loaded and the header and footer are the same. This is because the code of header and footer is shared among all the and making a change in one file will change it for all using that file.
Inside Views
we have another folder called Shared
and inside that we’ve _Layout.cshtml
open that.
Can you spot where the Privacy link is in the code above? Yes, it is on line 25. Let’s change line 25 with the one below:
<a class="nav-link text-dark" asp-area="" asp-controller="Employee" asp-action="Index">Employees</a>
In the code snippet above we have an HTML link tag i.e. a
with some bootstrap classes and the new things here asp-area
, asp-controller
, asp-action
are basically ASP.NET-related tags and you’ll know what they mean shortly. If you run the project you can see in the header the name privacy is replaced by employees and if you click it you’ll get an error. But you can click on the privacy link in the footer to see that the header remains the same on all pages.
Now close the browser and Right-click on the Models folder in Solution Explorer and then select Add > Class
Name this class Employee and click Add
In this class, we’ll create the model of our employee object. Add the following code in Employee.cs
In the code above we’re using System.ComponentModel.DataAnnotations;
which is a helpful built-in package that’ll help us in database migration and is responsible for this like [Key]
and [Required]
which clearly means that EmployeeId
is the primary key and the field that required a value and should be NOT NULL. If you’re familiar with SQL terms you’ll not what these attributes are doing. So this is the information that’ll be CRUD and Searched for.
Now right click on Models folder and select Add > Class and name this class IBJOfficeDbContext
This is the main class that’ll act as the model between your database connection and other classes. Add the following code in this class:
In this code, we have extended our IBJOfficeDbContext
class with DbCOntext
and then added a required constructor with some arguments. And lastly, we have initialized DbSet
type and given it the entity i.e. Employee
. DbSet
is used to query and save instances of the entity which in this case is Employee
.
Now open the Microsoft SQL Server Management Studio that we downloaded and installed.
I’ve given mine the name of localhost yours can be different. In some cases Windows automatically picks the server name and will display it here that is also fine what is important is that you select Windows Authentication and Database Engine and when you click connect it should connect and show you something similar to what is below:
The reason why we have connected to the server is that we’re about to make the connection and we need to know the server name for that.
Right-click on the localhost (…
and select Properties
Now here in properties, it’s important that you copy or write the server name somewhere (highlighted in red in the image below)
That’s it for our work on this software now let’s move back to Visual Studio 2019.
In solutions explorer, there will be a file named appsettings.json
open that and copy and add the following code to it.
"ConnectionStrings": {"DefaultConnection": "Server=_PUT_SERVER_NAME_HERE_;Database=IBJOffice;Trusted_Connection=True;MultipleActiveResultSets=true"},
Make sure to replace _PUT_SERVER_NAME_HERE_
with the server name you copied/noted down earlier the rest of the code will remain the same.
Now that we’re added the DefaultConnection
address to our appsettings.json
we also need to pass this connection refers to the application so that the application can use this to connect to our database.
To do that open Startup.cs
and add the following code inside public void ConfigureServices(IServiceCollection services)
method:
services.AddDbContext<IBJOfficeDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));services.AddControllersWithViews().AddRazorRuntimeCompilation();
In the first line of the code above we’ve added a database context to our services telling it to use SQL Server with connection information available in "DefaultConnection"
string that we posted in appsettings.json
earlier.
Second-line is not related to DB connection. I’ve just added .AddRazorRuntimeCompilation()
which will let your pages adopt HTML/CSS based changes just by refreshing the web page as we have installed this package at the start but doing this will actually make it applicable.
Now the stage is set to create a database in SQL. From menu select TOOLS > NuGet Package Manager > Package Manager Console
In the console type add-migration AddingEmployeesModelToDb
. The name AddingEmployeesModelToDb is something we write for our own reference as it logs the entry with this name so in larger projects, it’s easier to track down changes to DB.
After this press enter to execute and after it’s successfully done you’ll see that a new folder name Migrations is added in the solutions explorer and also a file with __somename___.cs
has been opened.
This .cs
file is created for you and this will create the database and table automatically for you rather than you going in the SQL Management Tool and creating the database and then table etc. This is taking the information you added in Employee.cs
and making sure that the primary key and all the required values are set in the database. But after this file is generated we have to tell our database that there has been a change so that the DB can update accordingly. So, next in package manager console type:
update-database
Now if you open SQL Server Management Tool you can see that the database is automatically created.
The database and employees table has been created for you. If you don’t see the database click on the refresh button and you’ll be able to see it. If you right-click on the table and select Select Top 1000 Rows you can see that the table has been created and with all the fields as we describe in Employee.cs
file.
Now let’s make a controller which will hold the logic of connecting this model to our views. Right-click on Controllers folder Add > Controller
Select MVC Controller - Empty and click Add
Name it EmployeeController. Naming is important in .NET so a controller will always be named as SOMETHINGController. Remember when we changed the line of code in _Layout.cshtml
we added a tag named asp-controller="Employee"
this tag will look for a controller named Employee
and that is why the naming is important.
Now, this controller needs a view because the view will send this controller the messages like Delete or Edit and others. So, right now we don’t have any view for our Employees. So let’s actually add the view using a shortcut method by right-clicking on the Index()
method in the EmployeeController.cs
that we’ve just made and selecting Add View...
Leave the settings as it is, just makes sure that the name of the view is Index.
Notice this will automatically create a new folder inside Views
named Employee
and inside that folder, you’ll see the file index.cshtml
. You can also do that manually by right-clicking Views and Add > New Folder and then on that folder right click and selecting Add > View and name it Index. Now if you run the app and click on the Employees button on the home page it’ll take you to this Index page that you have just created. The folder name is important as EmployeeController.cs
will look for Employee
folder inside Views
and inside the folder, it’ll look for index.cshtml
as the index is by default standards the name of the root page.
When you run and click on Employees it basically looks for EmployeeController
as we mentioned in line 25 of _Layout.cshtml
that asp-controller="Employee"
and the action name i.e. asp-action="Index"
so it’ll call the Index()
method inside the controller which is basically looking for a view inside Views folder with Folder name as Employee
and page name as Index
. I hope by now you understand why naming is important and how it guides the URL to form.
Just one more this with naming before we move forward if you open Startup.cs
and scroll down you see:
This is the default structure of your route. So that is why the Controller/Action/ matters in our case it’s Employee/Index and by default, it’ll look for Home controller and Index page in the Home folder of views.
Now that we have successfully made a route to our employee's page let’s start designing it as this is where we’ll be showing the table that will display the data fetched from DB and other things. But this is a good point to introduce you to some third-party libraries which provide built-in code to make your view look cool and fancy. So, it’ll be good practice for you to understand how to embed third-party libraries.
https://notifyjs.jpillora.com/ will be used to send notifications. Open the link and Download notify.min.js and create a new folder in /wwwroot/lib
named notify and drag and drop notify.min.js in it.
Now scroll down to the end of _Layout.cshtml
and paste the following lines anywhere between </footer>
and @Renderection("Scripts",.......)
:
<script src="~/lib/notify/notify.min.js"></script>
That’s it and you have successfully included the library inside of your project. The reason to include it in _Layout.cshtml
is because now it’s shared across all pages and we can use them on any view.
Let’s install another library called FontAwesome which is an awesome library that has text-based icons that makes your web-pages look pretty.
Open https://fontawesome.com/v4.7.0/get-started/ and scroll down to find the download button and download it.
Now unzip the downloaded file. Create a new folder in wwwroot
named fonts
inside this new folder create another folder named font-awesome
and inside it creates another folder named 4.7
and inside this folder drag and drop everything that was inside the unzipped folder.
Now add the following line in _Layout.cshtml
<link href="~/fonts/font-awesome/4.7/css/font-awesome.min.css" rel="stylesheet" />
You’ve just included two third-party libraries in your project.
Now open EmployeeController.cs
and here we’ll need a variable that’ll basically act as our database context so create a variable of type IBJOfficeDbCOntext
and initialize it with a constructor.
Right Click on Employee folder > Add > New Item and in that select TypeScript File and name it employee.ts
Inside this file add this code:
class Employees {}$(document).ready(function () {new Employees();});
You’ll see an error underneath $
sign. It’s because this is JQuery syntax and we need to install that.
Open TOOLS → NuGet Package Manager → Package Manager Console and type:
Install-Package jquery.TypeScript.DefinitelyTyped
After the console finished you see this yellow warning. Click on Install now and it’ll go away. If there are other warnings just close them and ignore them for now.
So installing this will resolve the error. Just to be on the safe side Run the project and see if it’s running successfully which it should at this point. Let’s leave this script here.
First, we need a helper script that’ll handle routine calls like add, edit, view, etc. So find wwwroot in solutions explore, right-click on js folder Add > New Item
Select JavaScript File and name it util.js
Add this code to this file:
var Util = (function (scope) {scope.request = function (url, type, dataType, successCallback, failureCallback, data) {try {if (data === undefined || data === null) {data = {};}$.ajax({type: type, //GET or POSTdataType: dataType, //html or jsonprocessData: true,data: data,url: url,statusCode: processResponse(successCallback, failureCallback),complete: function () {}});} catch (e) {console.error(e)}}
The above code is basically a variable that is equal to a function and what that function is doing, it’s handling requests coming from the controller. After this function ends we need to add a method named processResponse
which might be showing you an error at this time. So, let’s add this function right after the above variable functions are ends.
function processResponse(successCallback, failureCallback) {return {200: successCallback,401: handleUnauthorizedError,403: handleForbiddenError,404: failureCallback,500: failureCallback};}
Based on the response of the server our web page will show HTML error codes like 200, 401, etc you might be familiar with the 404 that you see on the websites. So we have given each of the error code a function that if that error occurs what should our program do. So we need to add two more functions as we need their definitions and those methods are handleUnauthorizedError
and handleForbiddenError
. So add the following code after the above:
function handleUnauthorizedError() {location.reload();}function handleForbiddenError() {$.notify('No permission to access.');}
The loction.reload()
refreshes the page and $.notify
message is basically getting handled by the notify.js
file you added before.
Lastly, add a return statement and close this JavaScript file with an empty object i.e. ({})
.
return scope;})({});
Here is the screenshot of the full util.js
file:
Now open _Layout.cshtml
and include this script in it as the line 48 highlighted in the image below:
Although this is created in order to use this, we need to have its access in our typescripts. So for that, we’ll add a small typescript file named declaration.ts
inside ~/Views/Shared
Inside this file just add this line:
declare let Util: any; //for util.js
That’s it! Now we have the required file to perform our CRUD and Search functionalities, you can say that we were setting up the ground and now the real implementation starts.
Fetching Data from SQL Database
Now we’ve entered the stage where we’ll be working towards fetching and displaying the data from SQL Database. So let’s work on our view. Open ~/Views/Employee/Index.cshtml
and remove everything that is in it and copy/paste the code on this link.
In the code above we’ve added our employee.js
as the browser cannot compile typescript. This employee.cs
will be created automatically by the employee.ts
that we’ll finish shortly.
Then the HTML code contains <div...
We’ve added the search bar, search button, add a new employee button, and then we’ve added a table in another <div>
. The class names are mostly bootstrap classes.
At the <table>
the important thing to see is the id="employee_list"
this is the ID that we’ll tell our script which we’ll code shortly that this is the table where you’ll show the employees list. You can also find another id’s like keyword
, search_employee
and add_employee
etc. These id
determine the section and are case sensitive. You’ll see shortly why they are super important.
Another thing that you’ll notice is the at the <tbody>
tag there is a comment it’s because we’ll get the body of this table from another view file. This is what .NET does it picks up code from different places and generates a single dynamic web page. Let’s create that view page by right-clicking on /Views/Employee
Add → View and name the table _TableData
and turn on the checkmark Create as partial view and click Add.
Inside this _TableData.cshtml
add the code on this link:
So as you can see on _TableData.cshtml
name starts with _
indicates when something is private. Now it’ll be shown on the public page but doing so gives us a better MVC approach and a secure way to load our data and then display the table.
At the start of this file, we’ve created a variable of typeList<Employee>
and then we’ve used a for loop to create a table row tr
where we are displaying table data td
and two buttons. Notice that there are buttons of edit and delete which are added as well but notice they don’t have an id but in class there is employee-edit
and employee-delete
as each row represents a different instance of data so we can’t use ID as ID is always unique and we’ve also assigned each button with a data called id
and give it the value of the employeeID
which will be super helpful in edit and delete operations.
Lastly, if there are no employees then our program will show No Records.
In order to see the code of these two CSHTML files use this link: https://gist.github.com/thechaudharysab/f5afdae91691e4cfa09a746719cfc99f
Now we’ll work on typescript. Open employee.ts
and inside class Employees {
add:
private urlGetData = "/employee/table-data-view";constructor() { this.init();}private init() { try { this.initTable(); } catch (e) { console.error(e); }}private initTable() { try { Util.request(this.urlGetData, 'GET', 'html', (response) => { $('#employees_list tbody').empty(); $('#employees_list tbody').append(response); }, function () { console.error('Failed to get data. Please try again'); }); } catch (e) { console.error(e); }}
Three things are happening in the code above. First, we’ve defined a custom URL i.e. /employee/table-view-data
. Don’t get confused this URL is just a way for us to tell the controller which route to call. It’ll get clear when we’ll work on EmployeeController.cs
shortly. Then we in the constructor we made a custom method named init()
and then we add a definition of that function in which we’re calling another custom function initTable()
. The reason why we are making so many custom functions is that it’ll make it easier in the future when we’ll be adding more functionalities like search, edit, add, etc.
Lastly, we have initTable()
in which we have called the util.js
i.e. Util.request
class that we made earlier, passed it some parameters like the urlGetData
and it’ll be a GET
call and we’re expecting an html
page in response. Now, in easy terms what’s happening is that we’re called for table-data as we made _TableData.cshtml
so basically we’re calling that here which is a CSHTML page. Now the important part as you read earlier that we gave employees_list
id to our table in Employee/Index.cshtml
, see how we’ve used it here with $('#employees_list tbody')
and I also added in the comments in the index file that the data of tbody
will come from a partial view so we have given that tag here tbody
to tell the compiler that we want the response you’re getting to be placed in a tag with id employees_list
and inside of that tag look for tbody
.
The first line inside Util.request
basically eliminates if there is any data available in the table to avoid duplication of data and the second line appends the response which in this case will be html
.
If you were to take any point from this long description it should be that the id name i.e. employees_list
and other tags' importance that they are the same as defined in HTML pages.
Now that we’re done with the script to view data the last thing left to do is to add code inside our EmployeeController.cs
as employee.ts
will be communicating our controller of what is required to display on the view so go ahead open up EmployeeController.cs
and edit the Index() method to the following:
[HttpGet("employee")]public IActionResult Index(){return View("~/Views/Employee/Index.cshtml");}
We’ve not done anything here just added [httpget("_url_")
and inside of the function where we were returning the ~/Views/Employee/Index.cshtml
already we’ve just explicitly added the path inside the return View()
.
Now remember the URL we’ve added in our employee.ts
i.e. /table-data-view
let’s implement that. After the Index()
function adds this function:
[HttpGet("employee/table-data-view")]public async Task<IActionResult> GetAllEmployees() {try{var data = await _db.Employees.ToListAsync();ViewData["EmployeesList"] = data;return PartialView("~/Views/Employee/_TableData.cshtml");}catch (Exception ex){Console.WriteLine(ex.Message);return null;}}
The first line is where we’ve added [HttpGet]
but this time the URL inside of it is different. Now when employee.ts
will run the code it knows that where to route the GET URL call and inside of this method we’re returning a view i.e. PartialView(..._TableData.cshtml)
. But before returning we’re doing another thing ViewData[“EmployeesList”] = data;
which if you open _TableData.cshtml
you can see on top that it’s basically the <List>Employee
so this is how you pass data from controller to a view. Our _db
is calling the Employees table and getting all the data in a list i.e. ToListAsync()
.
Here is a photo that’ll help you understand what we have done so far and how everything is connected:
When we click on Employee on our home page we’re basically calling Employee/Index.cshtml. The above image shows the whole process of what we have done so far in order to successfully display the employee index page.
Debugging
Now we’re about to test the functionality of the view. But there will be an error and that error will be because our employee.js
will return 404 that means that it’s not found and before we move into the solution I want you to see how to debug these type of errors which are in the runtime JavaScript.
Run the program make sure it runs on Chrome browser. When you see the home page press F12
and you’ll something as this opened:
There are different tabs in the pane that is opened. Select the Network tab and click on Employees from the menu on the page.
You’ll see an error showing up i.e. GET https://.....employee.js ERR_ABORTED 404
that means the employee.js
is not found.
But we know that our employee.js
is present in the project. Basically, this error occurs because by default the MVC block you from accessing CSS and js files from inside Views folders. We did it in this way because we wanted to put the js and/or any CSS related to a module to be together. Please note that this is not a required thing it’s just something I follow as it makes it easy when you’re making large web applications to find related code together. Now to fix this open Startup.cs
and after app.UseEndpoints(endpoints => {...});
ends add this:
// Render only .js and css files in "Views" folder.app.UseStaticFiles(new StaticFileOptions(){FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), @"Views")),RequestPath = new PathString("/Views"),ContentTypeProvider = new FileExtensionContentTypeProvider(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase){{".js", "application.javascript" },{".css", "text/css" }})});
Run it now and you should see this:
The No Records appears after a few seconds so do wait otherwise if there is no error in the visual studio then the only way to find the error is by press F12
and under the network tab.
Since there is no data inside the database it’s successfully showing No Records. That means everything is working, the Index.cshtml
is successfully loading the _TableData.cshtml
inside. Also, just to mention the search icon in the search button is coming from the FontAwesome library that we added, its code is in Index.cshtml
i.e. <i class=”fa fa-search mr-1"></i>
the fa-search is the icon.
At this point if you were to add a new module let’s say the organization we’re building this for is an NGO and there are volunteers. You should be able to create this module inside IBJOffice project where you can fetch the volunteers from database and only display them in a table. You’ll need a VolunteerController a folder in Views and inside the folder you’ll need their own Index, table data and typescript and a Model. By using the same DBContext you can achieve this and you’ll need to add a link in the header next to Employees that links to Volunteers page.
Adding Employee to Database
To add the user we’ll need a form. Just like we did with the _TableData
we’ll do the same by using partial view and you’ve already made a place for it inside Index/cshtml
page. Look for an id named employee_form
and read the comment inside of it.
Let’s create a partial view inside of ~/Views/Employee
folder and name it _AddForm
Copy/Paste the code in this file inside _AddForm.cshtml
So, in this file, we’ve created two variables one is an Employee object which will be null if we’re adding a new employee and it’ll have a value if we’re editing an employee. Where this data will come from you’ll see later. The next variable is called Title
and as we’re planning to use this same form for add and edit we’ll have to show the title accordingly to the action (for the sake of UX).
Next, we’ve shown a form inside a table which is not mandatory but it looks neat. Each input field has an ID and a value. In the case of edit when we’ll have the value for our @Employee.[Attributes]
they’ll populate in the fields but in the case of Add, the variable will have nothing so the values will be empty. Also, note that there is a line of code <input type="hidden"...
this is the employee in the field and because it’s the primary key which SQL will update automatically we have included it in the form because we’re using the same form for updating a record so we need a reference to the id in order to update a record.
Then at the end, we have two buttons for save and close. We’ll add their functionality shortly but as you can see the title of the save button is set accordingly. If the employee id will be zero that means it is a new employee that is being added so the title will be Create otherwise it’ll be Update.
Now as a next step we’ll need to add the click events that what to do when someone clicks on Add New Employee button in our employee.ts
.
First, we need to set a URL that so just how you added urlGetData
add anew variable called urlAddEmployee
and assign it the following value:
private urlAddEmployee = '/employee/add';
Next, In the private init() {...
method add this after this.initTable();
$('#add_employee').click(() => {this.add();});
What this code snippet above is doing that in the event of click on id add_employee
which you can find in /Views/Employee/Index.cshtml
page with the Add New Employee button. So this is basically calling a custom method add()
which we’ll make now. Add the following code after initTable()
method ends:
private add() {try {Util.request(this.urlAddEmployee, 'get', 'html', (response) => {$('#employee_form').html(response)this.initForm();}), () => {console.error('Failed to get data. Please try again');}} catch (e) {console.error(e);}}
Just like before we’re sending a request with the urlAddEmployee
which is a GET
request and expect some form of an HTML in response.
Inside the success block of the call, we’re handling the HTML event of the id employee_form
which you can also find the Employee index.cshtml
page. We’re adding the HTML response.
Lastly, we’re calling another function named initForm()
which will hold additional functionality and which is what we’re going to implement now. After Add()
function ends add this:
private initForm() {try {$('#save_form').click(() => {this.save();});$('#close_form').click(() => {location.reload(); //Reloads the page to show table});} catch (e) {console.error(e);}}
In this function, we’re handling the save_form
and close_form
functionalities. You can find this id’s inside _AddForm.cshtml
that you created earlier. So when these buttons will be clicked, their events will be handled here.
Note that we’re doing two functionality in this go. Until now we’ve implemented the functionality of displaying the form and now we’re going to add events of what will happen when the user press save. So, we need a URL that’ll be called when the user will press save. So add this URL to the top with other URL’s
private urlSaveEmployee = '/employee/save';
Let implement save()
so add the following code after the initForm()
function:
private save() {try {const employee = this.createEmployee();Util.request(this.urlSaveEmployee, 'post', 'json', (response) => {if (response != null) {$.notify(response.message);location.reload();} else {$.notify(response.message);console.error('Failed to get data #T7G985. Please try again.');}}, () => {}, employee);} catch (e) {console.error(e);}}
The first line is basically a parameter i.e. data that will be posted with the call meaning this is the employee object that we need to save in the database and this object is coming from createEmployee()
method which we’ll implement next.
As you can see this request is a POST request and expect a JSON response and if it’ll be successful we’ll show a success message otherwise a failed message.
Notice this line }, employee);
this is where we’ve passed the objecting employee that we made on the first line. As you’ll be seeing an error at the moment so let’s create the createEmployee()
function first:
private createEmployee() {try {const employee = {EmployeeId: $('#employee_id').val(),Firstname: $('#first_name').val(),Lastname: $('#last_name').val(),Position: $('#position').val(),Department: $('#department').val(),Salary: $('#salary').val(),DateJoined: $('#date_joined').val(),LastUpdated: $('#last_changed').val()};return employee;} catch (e) {console.error(e);}}
This is a dictionary object and the #id’s here are coming from _AddForm.cshtml
when we created this file we assigned the ids to every field. The names on the left side of :
should be the same as we have in Employee.cs
model file.
And now the last part is to make sure when these URLs are called the controller does its work. So open EmployeeController.cs
In this class, we need to implement two methods. One for showing the form and the other for saving. Let’s first implementing AddEmployeeForm()
[HttpGet("employee/add")]public IActionResult AddEmployeeForm() {try{ViewData["Title"] = "Add New Employee";ViewData["Employee"] = new Employee();return PartialView("~/Views/Employee/_AddForm.cshtml");}catch (Exception ex){Console.WriteLine(ex.Message);return null;}}
We’re returning the _AddForm.cshtml
page and before returning we’re setting the title and initializing a new employee object.
Now let’s implement the functionality of when someone clicks on save
[HttpPost("employee/save")]public IActionResult SaveEmployee(Employee employee) {try{_db.Employees.Add(employee);_db.SaveChanges();return Json(new { success = true, message = "Saved Successfully" });}catch (Exception ex){Console.WriteLine(ex.Message);return Json(new { success = false, message = "Error while saving" });}}}
As in our employee.ts
we know that the expected response will be JSON so we’re returning JSON. In the line the _db.Employees.Add(employee)
is adding employee object that is coming from the employee.ts
We’re now ready to test this so save everything and press F5
to run it and navigate to Employees and click on Add New Employee button and you’ll see the form loaded underneath the table.
Fill in the details and press save.
Deleting Employee to Database
The ground for delete functionality is already set so we’ll directly go to employee.ts
and add the URL for delete:
private urlDeleteEmployee = '/employee/delete';
Then inside initTable()
method add the following code after $(‘#employees_list tbody’).append(response);
line:
$(document).on("click", ".employee-delete", (e) => {const id = $(e.currentTarget).data('id');const data = { id: id };this.delete(data);});
Notice that instead of #
we have user .
in $('.employee-delete')
this is because if you find employee-delete
in _TableData.cshtml
file you’ll it’s under class='...'
and we represent a class with a .
and id with a #
also because id is unique and is assigned to one thing in the whole application where the call can be assigned to different things. Here class employee-delete
is assigned to every delete button because every delete button represents a different row of data. Also the tag after class='..'
tag in _TableData.cshtml
is data-id
where -id
is something we have custom-defined and data
is a built-in tag. We’ll use the id
tag to know which entry to delete as you’ll see next.
Next is you can guess we need to implement delete()
function.
private delete(data) {try {if (confirm("Are you sure you want to delete this employee?") == true) {Util.request(this.urlDeleteEmployee, 'post', 'json', () => {$.notify(`Employee deleted successfully.`);location.reload();}, () => {$.notify(`Failed to delete Employee. Please try again`);}, data);}} catch (e) {console.error(e);}}
And now we’ll go to our EmployeeController.cs
and add the following code:
[HttpPost("employee/delete")]public async Task<IActionResult> DeleteEmployee(int id){try{var employeeFromDb = await _db.Employees.FirstOrDefaultAsync(e => e.EmployeeId == id);if (employeeFromDb == null){return Json(new { success = false, message = "Error while deleting." });}_db.Employees.Remove(employeeFromDb);await _db.SaveChangesAsync();return Json(new { success = true, message = "Delete Successfully!" });}catch (Exception ex){Console.WriteLine(ex.Message);return Json(new { success = false, message = "Error while deleting." });}}
Now run the app and click on the delete button you should get a popup:
And pressing OK should delete it.
Editing Employee in Database
Just like delete most of the groundwork is ready for edit. So, start by creating the URL for edit in employee.ts
:
private urlEditEmployee = '/employee/edit';
Then in initTable()
add this:
$(document).on("click", ".employee-edit", (e) => {const id = $(e.currentTarget).data('id');const data = { id: id };this.edit(data);});
Here the .data('id')
the 'id'
has to be the same as we defined in _TableData.cshtml
→ data-id
here this id
after —
can be anything but make sure that it’s the same when passing it in typescript.
Now we’ll add edit()
function:
private edit(data) {try {Util.request(this.urlEditEmployee, 'get', 'html', (response) => {$('#employee_form').html(response)this.initForm();}, () => {console.error('Failed to get data. Please try again');}, data);} catch (e) {console.error(e);}}
This function is similar to the add function the only difference is the URL. So now we’ll handle this in the controller as this is where the difference between these two will become clear. So open EmployeeController.cs
and add the edit functions as:
[HttpGet("employee/edit")]public async Task<IActionResult> EditEmployee(int id) {try{Employee employee = await _db.Employees.FirstOrDefaultAsync(e => e.EmployeeId == id);if (employee == null){return NotFound();}ViewData["Title"] = "Edit Employee: " + employee.FirstName + " " + employee.LastName;ViewData["Employee"] = employee;return PartialView("~/Views/Employee/_AddForm.cshtml");}catch (Exception ex){Console.WriteLine(ex.Message);return Json(new { success = false, message = "Error while deleting." });}}
In the code, we’re first checking if the id passed is available in the database or not. And if it does we’re passing it to ViewData["Employee"]
in _AddForm.cshtml
. If you open _AddForm.cshtml
you can see a tag named value="...
this is where the value of ViewData["Employee"]
is being used. In add case, we don’t have it so that is why the field remains empty. If you run your program at this point and click on the edit button next to a record you’ll see that it is being fetched from the database.
But if you click on save right now it’ll add it as a new record rather than updating it. So for that, we need to modify our save method EmployeeController.cs
a little bit. Replace the save method with this new code:
[HttpPost("employee/save")]public async Task<IActionResult> SaveEmployee(Employee employee) {try{Employee employeeFromDb = await _db.Employees.FirstOrDefaultAsync(e => e.EmployeeId == employee.EmployeeId);if (employeeFromDb == null) {_db.Employees.Add(employee);_db.SaveChanges();return Json(new { success = true, message = "Created Successfully" });} else {employeeFromDb.FirstName = employee.FirstName;employeeFromDb.LastName = employee.LastName;employeeFromDb.Position = employee.Position;employeeFromDb.Department = employee.Department;employeeFromDb.Salary = employee.Salary;employeeFromDb.DateJoined = employee.DateJoined;employeeFromDb.LastUpdated = employee.LastUpdated;employeeFromDb.EmployeeId = employee.EmployeeId;_db.Employees.Update(employeeFromDb);_db.SaveChanges();return Json(new { success = true, message = "Updated Successfully" });}}catch (Exception ex){Console.WriteLine(ex.Message);return Json(new { success = false, message = "Error while saving" });}}
In the updated code above we’re first checking if a record exists in DB by comparing ids. Then if the record is not found that means it’s to add a new record but if the record is found we’re updating the found record from DB i.e. employeeFromDB
with the updated record coming from view input fields i.e. employee
and instead of add, we’re calling _db.Update(employeeFromDb)
.
Now run the program and try to change the value in a field. I’m changing the position from CEO to Manager in my case:
You’ll see it will update successfully.
Searching Employees
The search we’re going to implement basically search as we type. As the required view for search is already set we’ll just take a quick glance at the IDs related to the search. If you open Views/EmployeeIndex.cshtml
you can find the id associated with the search button is search_employee
and is attached with the input field of search is keyword
.
Open employee.ts
and add the URL for search.
private urlSearchEmployee = '/employee/search';
Then inside init()
method add
$('#search_employee').click(() => {const keyword = $('#keyword').val();this.search(keyword);});
As in the code above, we’re getting the value that’ll be in the field with id keyword
and passing this value to search(keyword)
function which we’ll implement next:
private search(keyword) {try {const data = { keyword: keyword };Util.request(this.urlSearchEmployee, 'GET', 'html', (response) => {const currentKeyWord = $('#keyword').val();if (currentKeyWord === keyword) {$('#employees_list tbody').empty();$('#employees_list tbody').append(response);}}, function () {$.notify('Failed to get data. Please try again.');console.error('Failed to get data #T09576. Please try again.');}, data);} catch (e) {console.error(e);}}
In the Util.request
part we're doing the same thing as we did in initTable
function. Then a small security check checks if the value in the search bar is the same as we got in the arguments and then we’re emptying the current table and updating it with the new filtered search result data.
Now, we’ll implement its controller part so in EmployeeController.cs
add this function:
[HttpGet("employee/search")]public async Task<IActionResult> SearchEmployee(string keyword) {try{var employees = await _db.Employees.Where(e =>e.LastName.ToLower().Contains(keyword.ToLower()) ||e.FirstName.ToLower().Contains(keyword.ToLower()) ||e.Position.ToLower().Contains(keyword.ToLower())).ToListAsync();ViewData["EmployeesList"] = employees;return PartialView("~/Views/Employee/_TableData.cshtml");}catch (Exception ex){Console.WriteLine(ex.Message);return null;}}
Same as [HttpGet(“employee/table-data-view”)]
we’ve implemented a search. The only difference would be we’re selecting the data where LastName
contains keyword
or FirstName
contains keyword
or Position
contains keyword
. And then we’re returning the selected list to view data and then finally we have called _TableData
view. Run it now and add a few records then search using the last name.
Then if I search
Congrats if you have made it this far because it was a long tutorial. So you’ve successfully completed View, Add, Edit, Delete and Search and now you can make any small-medium local web application. The approach I took is how most web applications I’ve worked on were built. In this tutorial, everything is stored locally but I’ll extend it so that you can also see how to deploy the web and make your web application LIVE in PART 2 of this.
As I always end by providing ideas/challenges that you can make using the knowledge that you have just acquired so here are some:
- Build a web application called Public Twitter in which whatever user posts will be public but users will need to login to post. So a login (user/pass stored in DB, it’s okay if unsecured at this point) and then a profile page on which their name, country, etc will be shown along with their posts. (Level: 🥵).
— To make this idea to a 😈 level read and see how to implement Twitter API so that these posts can be tweeted from your web application. Think that you’re building a local Twitter for the white house which will stay within the white house and Trump can tweet all he wants but it’ll be up to the staff of the white house to decide what goes on live Twitter. - Extend this tutorial where you add a new module for front desk people who will record data from anyone who visits the office. It’ll show/store information like name of person visiting, the reason for visiting, time of visit who they are meeting. The challenge is that you have to do this in the current project, like from home I click on employee it loads the employee's module just like this it should load front desk view and we can start from there. (Level 😬)
- Change the look and feel of the view pages of this tutorial and add more information about employees like their address. (Level: 😘)
The full code is available https://github.com/thechaudharysab/IBJOffice
Feel free to ask any questions, I’ll be happy to answer what I could and don’t forget to hit that clap button so that other people can also find it.