Share via


Getting Started with Entity Framework Core: Building an ASP.NET Core Application with Web API and Code First Development


Introduction

In the previous article, we’ve learned the high-level overview of what Entity Framework is all about and learned how to build a web application quickly using Entity Framework’s Database-First development.

This time we will build a simple, yet realistic ASP.NET Core application and showcase the features of Entity Framework Core. But before we dig down further, let’s take a quick recap about the high-level differences and key benefits of Code-First and Database-First design workflows then we’ll decide which we are going to use in our project.

Design Workflows

Just so you remember, there are two main design workflows that is supported by Entity Framework Core: The Code-First approach which is you create your classes (POCO Entities) and generate a new database out from it. The Database-First approach allows you to use an existing database and generate classes based on your database schema. The table below shows the high-level process of how each design workflow works.

 Code First (New Database) Database First (Existing Database)
   

Figure 1: EF Core Design Workflows

Code First (New Database)

  • Good option if you don't know the whole picture of your database as you can just update your Plain Old Class Object (POCO) entities and let EF sync your changes to your database. In other words, you can easily add or remove features defined in your class without worrying about syncing your database using Migrations.
  • You don't have to worry about your database as EF will handle the creation for you. In essence, a database is just a storage medium with no logic.
  • You will have full control over the code. You simply define and create POCO entities and let EF generate the corresponding Database for you. The downside is if you change something in your database manually, you will probably lose these changes because your code defines the database.
  • It is easy to modify and maintain as there will be no auto-generated code.

Database First (Existing Database)

  • Good option if you are good at database design or you have a database designed by DBAs, or you already have an existing database developed.
  • You don't have to worry about creating POCO objects as EF will generate them for you based on your existing database.
  • You can create partial classes if you want additional features in POCO entities.
  • You can manually modify your database because the database defines your POCO entities (domain model).
  • Provides more flexibility and control over database configuration.

Wrapping Up

You may go for the database-first route when you have an existing database developed separately or when time is important for you. Database first enables us to build applications faster as you can generate your domain models in just a few clicks. For larger applications or for long-term client projects, code first provides us the control we need to create the most efficient program and also gives us the protection and consistency of a versioned controlled database while reducing bloat.

Keep in mind that both design workflows have their advantages. Choosing which approach to use is very important decision to make when you start a new project, so be sure to know what your project requires and expects.

The table below outlines my own personal recommendation as to which design workflow to consider when building an application.

   Database First Code First 
Code Generation Create the database schema using your preferred tools (e.g SQL Server Management Studio) and import that database and schema using a script to scaffold everything for you. Write classes with properties you want to keep track of and then define relationships between other classes.
Syncing Changes Create a migration that represents the changes and runs the script against the database. You create the migrations when you need them, and the code will use those files (and a corresponding table in the target database) to keep the schema changes in sync with what the code expects. In other words, you can manage your database schema without ever writing any SQL.
Type of Project Best Suited for It makes more sense to use database-first for existing project since you don’t need to make big changes to scale the database.  You could also use this approach for new projects when a database is developed separately by DBA’s and owned by another team. Code-first makes more sense if you are developing a new application from scratch as you can grow and evolve your model as you develop.

Taking a Choice

As we can easily see by judging the differences between the two approaches, there is no such thing as an overall better or best approach: conversely, we could say that each project scenario will likely have a most suitable approach.

Now with regards to the application that we are going to build, considering the fact we don’t have a database yet and we’re aiming for a flexible, mutable small-scale data structure, adopting the Code-First approach would probably be a good choice. That’s what we’re going to do, build an ASP.NET Core application from scratch using Entity Framework Core's Code-First approach.

The ASP.NET Core Revolution

To summarize what happened in the ASP.NET world within the last year is not an easy task: in short words, we could say that we’re undoubtedly facing the most important series of changes in the .NET Framework since the year it came to life. ASP.NET Core is a complete re-implementation of ASP.NET which unites all the previous web application technologies such as MVC, Web API and Razor Pages into a single programming module, formerly known as MVC6. The new framework introduces a fully-featured cross-platform component, also known as .NET Core, shipped with a brand-new open-source .NET Compiler Platform (currently known as Roslyn), a cross-platform runtime known as CoreCLR.

ASP.NET Core is a new open-source and cross-platform framework for building modern cloud-based web applications. It has been built from the ground up to provide an optimized development framework for web apps that are either deployed to the cloud or to your local servers. Adding to that, it was redesigned to make ASP.NET leaner, modular (so you can just add features that your application requires), cross-platform (so you can easily develop and run your app on Windows, Mac or Linux that is pretty awesome) and cloud-optimized (so you can deploy and debug apps over the cloud).

Three Players, One Goal

Our main goal is to build a data-driven web application using the three cutting-edge technologies: ASP.NET Core MVC, Web API, and Entity Framework Core.

ASP.NET Web API is a framework used to build HTTP services and is an ideal platform for building RESTful applications on the .NET Framework.

ASP.NET Core MVC is a pattern-based way to build dynamic websites that enables a clean separation of concerns and that gives you full control over markup or HTML.

We will use ASP.NET Core to build  REST API's (Web API) and as our front-end framework (MVC) to generate pages or views (a.k.a User Interface). On the other hand, we will be using Entity Framework Core as our data access mechanism to work with data from the database. The figure below illustrates how each technology interacts with each other.


Figure 2: High-level process flow

The figure above illustrates how the process works when a user accesses information from the database. When a user requests a page, it will bring up the View/UI (MVC). When a user wants to view some data, MVC app will then talk to the Web API service to process it. Web API will then talk to EF Core to handle the actual request (CRUD operations) and then updates the database when necessary.

Setting Up an ASP.NET Core Web API Project

Now that we already know the high-level overview of ASP.NET Core and EF Core, it’s time for us to get our hands dirty by building a simple, yet data-driven web application using ASP.NET Core with Entity Framework Core. Let’s create the Web API project first.

Fire-up Visual Studio 2017 and let’s create a new ASP.NET Core Web Application project. To do this, select File > New > Project. In the New Project dialog select Visual C# > Web > ASP.NET Core Web Application (.NET Core) just like in the figure below:


*Figure 3: Create New ASP.NET Core Project *

In the dialog, enter the name of your project and then click the OK button to proceed to the next step. Note that for this specific demo we named the project as "StudentApplication.API". Now click on Web API template as shown in the figure below.


Figure 4: ASP.NET Core Web API Template

With the new ASP.NET Core project template dialog, you can now choose to use ASP.NET Core 1.0 or 1.1 using the combo-box at the top of the screen. The default template as of this time of writing is ASP.NET Core 1.1. Additionally, you can choose to add Container support to your project with the checkbox at the bottom-left of the screen.  This option adds:

  • Docker files to your project for building Container images
  • a Docker Compose project to define how you instance your Containers
  • Multi-project/container debugging support for Containers
  • For the simplicity of this exercise, let’s just omit the use of containers.

Now click again the OK button to let Visual Studio generate the default files for you. The figure below shows the generated files:


Figure 5: Visual Studio Default Generated Files

Let’s take a quick overview of all generated file.

If you already know the core significant changes of ASP.NET Core then you may skip this part, but if you are new to ASP.NET Core then I would like to highlight some of those changes. If you have worked with previous versions of ASP.NET before then you will notice that the new project structure is totally different. The project now includes these files:

  • Connected Services: allow service providers to create Visual Studio extensions that can be added to a project without leaving the IDE. It also allows you to connect your ASP.NET Core Application or Mobile Services to Azure Storage services. Connected Services takes care of all the references and connection code, and modifies your configuration files accordingly
  • Controllers: this is where you put all your Web API or MVC controller classes. Note that the Web API project will by default generate the ValuesController.cs file.
  • wwwroot: is a folder in which all your static files will be placed. These are the assets that the web app will serve directly to the clients, including HTML, CSS, Image and JavaScript files.
  • appsettings.json: contains application settings.
  • Startup.cs: this is where you put your startup and configuration code.
  • Program.cs: this is where you initialize all services needed for your application.

Running the Project for the First Time

To ensure that we got everything we need for our Web API project, let’s try to build and run the project.  We can do this by pressing the CTRL + F5 keys. This will compile, build and automatically launches the browser window. If you are getting the following result below, then we can safely assume that our project is good to go.


Figure 6: Initial Run

Setting Up Entity Framework Core

Let’s install Entity Framework Core packages into our project. Go to Tools > NuGet Package Manager -> Package Manager Console and run the following command individually:

Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools

After successfully installing the packages above, make sure to check your project references to ensure that we got them in the project. See the figure below.


Figure 7: Entity Framework Core Package References

These packages are used to provide Entity Framework Core migration. Migration enables us to generate database, sync and update tables based on our Entity.

Creating the Model

We will start by creating the “Models” folder within the root of the application. Add a new class file to the Models folder and name it as “Student.cs”, then replace the default generated code with the following:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
 
namespace StudentApplication.API.Models
{
    public class  Student
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public long  StudentId { get; set; }
        public string  FirstName { get; set; }
        public string  LastName { get; set; }
        public string  Gender { get; set; }
        public DateTime? DateOfBirth { get; set; }
        public DateTime? DateOfRegistration { get; set; }
        public string  PhoneNumber { get; set; }
        public string  Email { get; set; }
        public string  Address1 { get; set; }
        public string  Address2 { get; set; }
        public string  City { get; set; }
        public string  State { get; set; }
        public string  Zip { get; set; }
    }
}

The code above is nothing but just a simple class that houses some properties. You may have noticed that we decorated the StudentId property with Key and DatabaseGenerated attributes. This is because we will be converting this class into a database table and the StundentId will serve as our primary key with auto-incremented identity. These attributes reside within System.ComponentModel.DataAnnotations. For more details about Data Annotations see:  Using Data Annotations for Model Validation

Creating an Empty Database

Now it's time for us to create our database. Open SQL Express Management Studio and then execute the following command:

CREATE DATABASE  StundentApplication

The command above should create a blank database in your local machine.

Defining a DBContext

Entity Framework Core Code-First development approach requires us to create a data access context class that inherits from the DbContext class. Now let's add a new class to the Models folder. Name the class as "ApplicationContext" and then copy the following code below:

using Microsoft.EntityFrameworkCore;
 
namespace StudentApplication.API.Models
{
    public class  ApplicationContext: DbContext
    {
        public ApplicationContext(DbContextOptions opts) : base(opts)
        {
        }
 
        public DbSet<Student> Students { get; set; }
        protected override  void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
        }
    }
}

The code above defines a context and an entity class that makes up our model. A DbContext must have an instance of DbContextOptions in order to execute. This can be configured by overriding OnConfiguring or supplied externally via a constructor argument. In our example above, we opt to choose constructor argument for configuring our DbContext.

Registering the DbContext with Dependency Injection

Our ApplicationContext class will act as a service. This service will be registered with Dependency Injection (DI) during application startup. This would enable our API Controller or other services gain access to the ApplicationContext via constructor parameters or properties.

In order for other services to make use of ApplicationContext, we will register it as a service. To enable the service, do following steps:

  1. Define the following database connection string under appsettings.json file:
,
  "ConnectionString": {
    "StudentApplicationDB": "Server=SSAI-L0028-HP\\SQLEXPRESS;Database=StundentApplication;Trusted_Connection=True;"
  }
  1. Open Startup.cs and declare the following namespace references:
using Microsoft.EntityFrameworkCore;
using StudentApplication.API.Models;
using StudentApplication.API.Models.DataManager;
using StudentApplication.API.Models.Repository
  1. Add the following code within ConfigureServices method to register our ApplicationContext as a service.
public void  ConfigureServices(IServiceCollection services)
{
     // Add framework services.
     services.AddDbContext<ApplicationContext>(opts => opts.UseSqlServer(Configuration["ConnectionString:StudentApplicationDB"]));
     services.AddSingleton(typeof(IDataRepository<Student, long>), typeof(StudentManager));     
     services.AddMvc();
}

Adding Migrations

Our next step is to add Code-First migrations. Migrations automate the creation of database based on our Model. Run the following command in the PM console:

PM> Add-Migration StudentApplication.API.Models.ApplicationContext

When the command is executed successfully, it should generate the migration files as shown in the figure below:
*

Figure 8: Generated EF Migration Files*

Now, switch back to the Package Manager Console and run the following command:

PM> update-database

The command above should generate the database table based on our Entity. Here’s the sample screenshot of the generated database table:


Figure 9: Generated Table

Implementing a Simple Data Repository

We don't want our API Controller to access directly our ApplicationContext service, instead, we will let other services handle the communication between our ApplicationContext and API Controller. Having said that, we are going to implement a basic generic repository for handling data access within our application. Add a new folder under Models and name it as “Repository”. Create a new interface and name it as “IDataRepository”. Update the code within that file so it would look similar to the following code below:

using System.Collections.Generic;
 
namespace StudentApplication.API.Models.Repository
{
    public interface  IDataRepository<TEntity, U> where TEntity : class
    {
        IEnumerable<TEntity> GetAll();
        TEntity Get(U id);
        long Add(TEntity b);
        long Update(U id, TEntity b);
        long Delete(U id);
    }
}

The code above defines our IDataRepository interface. An interface is just a skeleton of a method without the actual implementation. This interface will be injected into our API Controller so we will only need to talk to the interface rather than the actual implementation of our repository. One of the main benefits of using an interface is to make our code reusable and easy to maintain.

Creating the DataManager

Next is we are going to create a concrete class that implements the IDataRepository interface. Add a new folder under Models and name it as “DataManager”. Create a new class and name it as “StudentManager”. Update the code within that file so it would look similar to the following code below:

using StudentApplication.API.Models.Repository;
using System.Collections.Generic;
using System.Linq;
 
namespace StudentApplication.API.Models.DataManager
{
    public class  StudentManager : IDataRepository<Student, long>
    {
        ApplicationContext ctx;
        public StudentManager(ApplicationContext c)
        {
            ctx = c;
        }
 
        public Student Get(long id)
        {
            var student = ctx.Students.FirstOrDefault(b => b.StudentId == id);
            return student;
        }
 
        public IEnumerable<Student> GetAll()
        {
            var students = ctx.Students.ToList();
            return students;
        }
 
        public long  Add(Student stundent)
        {
            ctx.Students.Add(stundent);
            long studentID = ctx.SaveChanges();
            return studentID;
        }
 
        public long  Delete(long  id)
        {
            int studentID = 0;
            var student = ctx.Students.FirstOrDefault(b => b.StudentId == id);
            if (student != null)
            {
                ctx.Students.Remove(student);
                studentID = ctx.SaveChanges();
            }
            return studentID;
        }
 
        public long  Update(long  id, Student item)
        {
            long studentID  = 0;
            var student = ctx.Students.Find(id);
            if (student != null)
            {
                student.FirstName = item.FirstName;
                student.LastName = item.LastName;
                student.Gender = item.Gender;
                student.PhoneNumber = item.PhoneNumber;
                student.Email = item.Email;
                student.DateOfBirth = item.DateOfBirth;
                student.DateOfRegistration = item.DateOfRegistration;
                student.Address1 = item.Address1;
                student.Address2 = item.Address2;
                student.City = item.City;
                student.State = item.State;
                student.Zip = item.Zip;
 
                studentID = ctx.SaveChanges();
            }
            return studentID;
        }
    }
}

The DataManager class handles all database operations for our application. The purpose of this class is to separate the actual data operation logic from our API Controller, and to have a central class for handling create, update, fetch and delete (CRUD) operations.

At the moment, the DataManager class contains five methods: The Get() method gets a specific student record from the database by passing an Id. It uses a LINQ syntax to query the data and returns a Student object. The GetAll() method gets all students records from the database as you could probably guess based on the method name. The Add() method creates a new student record in the database. The Delete() method removes a specific student record from the database based on the Id. Finally, the Update() method updates a specific student record in the database. All methods above use LINQ to query the data from the database.

Creating an API Controller

Now that our DataManager is all set, it's time for us to create the API Controller and expose some endpoints for handling CRUD operations. 

Go ahead and right click on the Controllers folder and then select Add > New Item > Web API Controller class. Name the class as "StudentController" just like in the figure below and then click Add.


Figure 10: New Web API Controller Class

Now replace the default generated code so it would look similar to the following code below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using StudentApplication.API.Models;
using StudentApplication.API.Models.Repository;
using StudentApplication.API.Models.DataManager;
 
 
namespace StudentApplication.API.Controllers
{
    [Route("api/[controller]")]
    public class  StudentController : Controller
    {
        private IDataRepository<Student,long> _iRepo; 
        public StudentController(IDataRepository<Student, long> repo)
        {
            _iRepo = repo;
        }
 
        // GET: api/values
        [HttpGet]
        public IEnumerable<Student> Get()
        {
            return _iRepo.GetAll();
        }
 
        // GET api/values/5
        [HttpGet("{id}")]
        public Student Get(int id)
        {
            return _iRepo.Get(id);
        }
 
        // POST api/values
        [HttpPost]
        public void  Post([FromBody]Student student)
        {
            _iRepo.Add(student);
        }
 
        // POST api/values
        [HttpPut]
        public void  Put([FromBody]Student student)
        {
            _iRepo.Update(student.StudentId,student);
        }
 
        // DELETE api/values/5
        [HttpDelete("{id}")]
        public long  Delete(int  id)
        {
            return _iRepo.Delete(id);
        }
    }
}

The StudentController derives from the Controller base and by decorating it with the Route attribute enables this class to become a Web API Controller.

If you notice, we have used Controller Injection to subscribed to the IDataRepository interface. So instead of taking directly to the StudentManager class, we just simply talk to the interface and make use of the methods that are available. 

The StudentController has five (5) main methods/endpoints. The first Get() method class the GetAll() method from IDataRepository interface and basically returns all the list of Student from the database. The second Get() method returns a specific Student object based on the Id. Notice that the second Get() method is decorated with [HttpGet("{id}")], which means append the "id" to the route template. The "{id}" is a placeholder variable for the ID of the Student object. When the second Get() method is invoked, it assigns the value of "{id}" in the URL to the method's id parameter. The Post() method adds a new record to the database. The Put() method updates a specific record from the database. Notice that both Post() and Put() uses a [FromBody] tag. When a parameter has [FromBody], Web API uses the Content-Type header to select a formatter. Finally, the Delete() method removes a specific record from the database.

Creating an ASP.NET Core MVC Application

At this point, we already have all the requirement we need to build the UI part of this demo. First we created the database, set up our data access using Entity Framework Core Code-First and finally, we already have our Web API ready. Now it's time for us to create a web application that consumes our Web API endpoints.

Now go ahead and create a new ASP.NET Core project. Right click on the Solution and then select Add > New Project > ASP.NET Core Web Application (.NET Core). You should be able to see something like this:


Figure 11: New ASP.NET Core Web Application Project

To be more consistent, name the project as "StudentApplication.Web" and then click OK. In the next dialog, select "Web Application" under ASP.NET Core 1.1 templates and then click OK to let Visual Studio generate the default files for us.

Integrating Newtonsoft.Json

Let's continue by installing Newtonsoft.Json package in the project. Right-click on the project and then select Manage Nuget Packages. In the browse tab, type in "NewtonSoft" and it should result in something like this:


Figure 12: Adding Newtonsoft.Json Package

Click "install" to add the latest package version to the project.

We will be using Newtonsoft.Json later in our code to serialize and deserialize an object from an API request.

The Helper Class

Create a new class at the root of the application and name it as "Helper.cs" and then copy the following code below:

using System;
using System.Net.Http;
using System.Net.Http.Headers;
 
namespace StudentApplication.Web.Helper
{
 
    public class  StudentAPI
    {
        private string  _apiBaseURI = "http://localhost:60883";
        public HttpClient InitializeClient()
        {
            var client = new  HttpClient();
            //Passing service base url  
            client.BaseAddress = new  Uri(_apiBaseURI);
 
            client.DefaultRequestHeaders.Clear();
            //Define request data format  
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
 
            return client;
 
        }
    }
 
    public class  StudentDTO
    {
        public long  StudentId { get; set; }
        public string  FirstName { get; set; }
        public string  LastName { get; set; }
        public string  Gender { get; set; }
        public DateTime? DateOfBirth { get; set; }
        public DateTime? DateOfRegistration { get; set; }
        public string  PhoneNumber { get; set; }
        public string  Email { get; set; }
        public string  Address1 { get; set; }
        public string  Address2 { get; set; }
        public string  City { get; set; }
        public string  State { get; set; }
        public string  Zip { get; set; }
    }
}

The Helper.cs file contains two (2) main classes: The StudentAPI and StudentDTO. The StudentAPI class has a method InitializeClient() to initialize and HttpClient object. We will be needing this method later to access the API endpoints. The StudentDTO is nothing but a plain class that mimics the Student class from our API Controller. We will be using this class as object parameter for API request.

Note: When testing this in your local, you must change the value in the _apiBaseURI variable to where your Web API project is hosted, or if you are running locally using IISExpress then change the value of the port number generated by Visual Studio.

The Controller

Add a new controller under the Controllers folder and name it as "StudentsController". Replace the default generated code with the following code below:

using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using StudentApplication.Web.Helper;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
 
namespace StudentApplication.Web.Controllers
{
    public class  StudentsController : Controller
    {
        StudentAPI _studentAPI = new  StudentAPI();
 
        public async Task<IActionResult> Index()
        {
            List<StudentDTO> dto = new  List<StudentDTO>();
 
            HttpClient client = _studentAPI.InitializeClient();
 
            HttpResponseMessage res = await client.GetAsync("api/student");
 
            //Checking the response is successful or not which is sent using HttpClient  
            if (res.IsSuccessStatusCode)
            {
                //Storing the response details received from web api   
                var result = res.Content.ReadAsStringAsync().Result;
 
                //Deserializing the response received from web api and storing into the Employee list  
                dto = JsonConvert.DeserializeObject<List<StudentDTO>>(result);
 
            }
            //returning the employee list to view  
            return View(dto);
        }
 
        // GET: Students/Create
        public IActionResult Create()
        {
            return View();
        }
 
        // POST: Students/Create
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Create([Bind("StudentId,FirstName,LastName,Gender,DateOfBirth,DateOfRegistration,PhoneNumber,Email,Address1,Address2,City,State,Zip")] StudentDTO student)
        {
            if (ModelState.IsValid)
            {
                HttpClient client = _studentAPI.InitializeClient();
 
                var content = new  StringContent(JsonConvert.SerializeObject(student), Encoding.UTF8,"application/json");
                HttpResponseMessage res = client.PostAsync("api/student", content).Result;
                if (res.IsSuccessStatusCode)
                {
                    return RedirectToAction("Index");
                }
            }
            return View(student);
        }
 
        // GET: Students/Edit/1
        public async Task<IActionResult> Edit(long? id)
        {
            if (id == null)
            {
                return NotFound();
            }
 
            List<StudentDTO> dto = new  List<StudentDTO>();
            HttpClient client = _studentAPI.InitializeClient();
            HttpResponseMessage res = await client.GetAsync("api/student");
  
            if (res.IsSuccessStatusCode)
            { 
                var result = res.Content.ReadAsStringAsync().Result;
                dto = JsonConvert.DeserializeObject<List<StudentDTO>>(result);
            }
 
            var student = dto.SingleOrDefault(m => m.StudentId == id);
            if (student == null)
            {
                return NotFound();
            }
 
            return View(student);
        }
 
        // POST: Students/Edit/1
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Edit(long id, [Bind("StudentId,FirstName,LastName,Gender,DateOfBirth,DateOfRegistration,PhoneNumber,Email,Address1,Address2,City,State,Zip")] StudentDTO student)
        {
            if (id != student.StudentId)
            {
                return NotFound();
            }
 
            if (ModelState.IsValid)
            {
                HttpClient client = _studentAPI.InitializeClient();
 
                var content = new  StringContent(JsonConvert.SerializeObject(student), Encoding.UTF8, "application/json");
                HttpResponseMessage res = client.PutAsync("api/student", content).Result;
                if (res.IsSuccessStatusCode)
                {
                    return RedirectToAction("Index");
                }
            }
            return View(student);
        }
 
        // GET: Students/Delete/1
        public async Task<IActionResult> Delete(long? id)
        {
            if (id == null)
            {
                return NotFound();
            }
 
            List<StudentDTO> dto = new  List<StudentDTO>();
            HttpClient client = _studentAPI.InitializeClient();
            HttpResponseMessage res = await client.GetAsync("api/student");
 
            if (res.IsSuccessStatusCode)
            {  
                var result = res.Content.ReadAsStringAsync().Result; 
                dto = JsonConvert.DeserializeObject<List<StudentDTO>>(result);
            }
 
            var student = dto.SingleOrDefault(m => m.StudentId == id);
            if (student == null)
            {
                return NotFound();
            }
 
            return View(student);
        }
 
        // POST: Students/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public IActionResult DeleteConfirmed(long id)
        {
            HttpClient client = _studentAPI.InitializeClient();
            HttpResponseMessage res = client.DeleteAsync($"api/student/{id}").Result;
            if (res.IsSuccessStatusCode)
            {
                return RedirectToAction("Index");
            }
 
            return NotFound();
        }
 
    }
}

Let's see what we just did above. 

Notice that our MVC Controller uses an HttpClient object to access our Web API endpoints. We also use the Newtonsoft's JsonConvert.SerializeObject() and JsonConvert.DeserializeObject() to serialize and deserialize the data back and forth.

The Index() action method gets all Student records from the database. This method is defined as asynchronous which returns the corresponding Index View along with the list of Students entity.

The Create() action method simply returns it's corresponding View. The overload Create() action method is decorated with the [HttpPost] attribute which signifies that the method can only be invoked for POST requests. This method is where we actually handle the adding of new data to database if the model state is valid on posts.

The Edit() action method takes an id as the parameter. If the id is null, it returns NotFound(), otherwise it returns the corresponding data to the View. The overload Edit() action method updates the corresponding record to the database based on the id.

Just like the other action, the Delete() action method takes an id as the parameter. If the id is null, it returns NotFound(), otherwise it returns the corresponding data to the View. The overload Delete() action method deletes the corresponding record to the database based on the id.

The Views

The following are the corresponding markup for Index, Create, Edit and Delete Views.

Index

@model IEnumerable<StudentApplication.Web.Helper.StudentDTO>
 
    @{
        ViewData["Title"] = "Index";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
 
    <h2>Index</h2>
 
    <p>
        <a asp-action="Create">Create New</a>
    </p>
    <table class="table">
        <thead>
            <tr>
                <th>
                    @Html.DisplayNameFor(model => model.FirstName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.LastName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.Gender)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.DateOfBirth)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.DateOfRegistration)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.PhoneNumber)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.Email)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.Address1)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.Address2)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.City)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.State)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.Zip)
                </th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @foreach (var item in Model)
            {
                <tr>
                    <td>
                        @Html.DisplayFor(modelItem => item.FirstName)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.LastName)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Gender)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.DateOfBirth)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.DateOfRegistration)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.PhoneNumber)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Email)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Address1)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Address2)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.City)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.State)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Zip)
                    </td>
                    <td>
                        <a asp-action="Edit" asp-route-id="@item.StudentId">Edit</a> |
                        <a asp-action="Details" asp-route-id="@item.StudentId">Details</a> |
                        <a asp-action="Delete" asp-route-id="@item.StudentId">Delete</a>
                    </td>
                </tr>
            }
        </tbody>
    </table>

Create

@model StudentApplication.Web.Helper.StudentDTO
 
@{
    ViewData["Title"] = "Create";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
<h2>Create</h2>
 
<form asp-action="Create">
    <div class="form-horizontal">
        <h4>Student</h4>
        <hr />
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <div class="form-group">
            <label asp-for="FirstName" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="FirstName" class="form-control" />
                <span asp-validation-for="FirstName" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="LastName" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="LastName" class="form-control" />
                <span asp-validation-for="LastName" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Gender" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Gender" class="form-control" />
                <span asp-validation-for="Gender" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="DateOfBirth" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="DateOfBirth" class="form-control" />
                <span asp-validation-for="DateOfBirth" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="DateOfRegistration" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="DateOfRegistration" class="form-control" />
                <span asp-validation-for="DateOfRegistration" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="PhoneNumber" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="PhoneNumber" class="form-control" />
                <span asp-validation-for="PhoneNumber" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Email" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Address1" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Address1" class="form-control" />
                <span asp-validation-for="Address1" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Address2" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Address2" class="form-control" />
                <span asp-validation-for="Address2" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="City" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="City" class="form-control" />
                <span asp-validation-for="City" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="State" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="State" class="form-control" />
                <span asp-validation-for="State" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Zip" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Zip" class="form-control" />
                <span asp-validation-for="Zip" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
</form>
 
<div>
    <a asp-action="Index">Back to List</a>
</div>
 
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Edit

@model StudentApplication.Web.Helper.StudentDTO
 
@{
    ViewData["Title"] = "Edit";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
<h2>Edit</h2>
 
<form asp-action="Edit">
    <div class="form-horizontal">
        <h4>Student</h4>
        <hr />
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <input type="hidden" asp-for="StudentId" />
        <div class="form-group">
            <label asp-for="FirstName" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="FirstName" class="form-control" />
                <span asp-validation-for="FirstName" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="LastName" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="LastName" class="form-control" />
                <span asp-validation-for="LastName" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Gender" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Gender" class="form-control" />
                <span asp-validation-for="Gender" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="DateOfBirth" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="DateOfBirth" class="form-control" />
                <span asp-validation-for="DateOfBirth" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="DateOfRegistration" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="DateOfRegistration" class="form-control" />
                <span asp-validation-for="DateOfRegistration" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="PhoneNumber" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="PhoneNumber" class="form-control" />
                <span asp-validation-for="PhoneNumber" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Email" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Address1" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Address1" class="form-control" />
                <span asp-validation-for="Address1" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Address2" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Address2" class="form-control" />
                <span asp-validation-for="Address2" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="City" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="City" class="form-control" />
                <span asp-validation-for="City" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="State" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="State" class="form-control" />
                <span asp-validation-for="State" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Zip" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Zip" class="form-control" />
                <span asp-validation-for="Zip" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
</form>
 
<div>
    <a asp-action="Index">Back to List</a>
</div>
 
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Delete

@model StudentApplication.Web.Helper.StudentDTO
 
@{
    ViewData["Title"] = "Delete";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
<h2>Delete</h2>
 
<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="dl-horizontal">
        <input type="hidden" asp-for="StudentId" />
        <dt>
            @Html.DisplayNameFor(model => model.FirstName)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.FirstName)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.LastName)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.LastName)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Gender)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Gender)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.DateOfBirth)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.DateOfBirth)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.DateOfRegistration)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.DateOfRegistration)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.PhoneNumber)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.PhoneNumber)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Email)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Email)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Address1)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Address1)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Address2)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Address2)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.City)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.City)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.State)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.State)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Zip)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Zip)
        </dd>
    </dl>
 
    <form asp-action="Delete">
        <div class="form-actions no-color">
            <input type="submit" value="Delete" class="btn btn-default" /> |
            <a asp-action="Index">Back to List</a>
        </div>
    </form>
</div>

All markup above is a strongly-typed View as all of them are referencing the same object Model which is StudentApplication.Web.Helper.StudentDTO. They also used tag-helpers to define the HTML and data binding.

Testing the Application

Since this demo is composed of two projects and our MVC application relies on our Web API application, we need to make the Web API accessible when testing the page. Typically, we will host or deploy both projects in IIS web server to be able to connect between projects. Luckily, one of the cool features in Visual Studio 2017 is to enable multiple start-up projects. This means we could run both our Web API and MVC application within Visual Studio and be able to test them right away without having them to deploy in IIS. All you need to do is:

  1. Right click on the Solution
  2. Select Set Startup Projects
  3. Select Multiple Startup Projects radio button
  4. Select "Start" as the action for both Web API and MVC project
  5. Click Apply and then OK

Now build and run the application using CTRL + 5 keys. In the browser, navigate to /students/Create. It should bring up the following page.


Figure 13: The Create View

Supply the fields and click the "Create" button and it should navigate you to the Index page with the new student record added as shown in the figure below:


Figure 14: The Index View

Of course the Edit and Delete works too :) Feel free to download the source code and have fun!

Summary

In this series, we’ve learned to build an ASP.NET Core MVC application using Entity Framework Core with a new database. We also learned how to create a Web API Controller and have the Controller talked to an Interface. We also learned how to implement a simple Data Access using a repository pattern to handle CRUD operations. Finally, we've learned how to create a basic ASP.NET Core MVC application that consumes a Web API endpoints to perform CRUD in the UI. 

Source Code

Feel free to download the source code at the Gallery section here: EF Core Code-First Development  

See Also