Learning Objectives

In this tutorial we’ll create an ASP .NET Core 3.0 web application using MVC, Entity Framework, and a restful Web API.

By the end of the article, we’ll have an operational web application that lets us create and modify test suites and add manage test cases in each test suite.

We’ll focus on getting up and running quickly and will largely go with the default look and feel for new web applications.

Along the way we’ll discuss:

  • ASP .NET Core 3.0
  • Model View Controller (MVC)
  • Entity Framework
  • Web API

Understanding ASP .NET Core 3.0 MVC Web Apps

ASP .NET Core 3.0 is a web server running on .NET Core 3.0. This is an enterprise-ready web server capable of serving up static or dynamic web pages as well as API endpoints.

ASP .NET offers a variety of web page technologies but in this article we’ll focus on their Model View Controller (MVC) offering with its Razor syntax.

This is not an article about understanding the details of MVC or Razor, but we will be looking at some code related to these things.

Model View Controller consists of the following components:

  • The Model, or class, containing the data
  • The View, or web page, presenting the data
  • The Controller, a class that handles requests from the view, modifies the Model, and directs to an updated View.

Razor Views use a templating engine built on top of HTML that allows for various directives. In this article we’ll be focusing exclusively on HTML helpers and letting the scaffolding features of Visual Studio generate the rest of the view for us.

The other technology we’ll be working with is Entity Framework, which is a .NET wrapper around databases that can handle dynamic querying and database migrations.

Let’s get started.

Creating the Project

In Visual Studio 2019, create a new project and select an ASP.NET Core web application project.

Create a new project dialog with ASP .NET Core Web Application selected

There are a lot of different flavors of ASP .NET, but for this tutorial we’ll be going with an ASP .NET Core 3.0 Web Application (Model-View-Controller) application.

Create a new ASP.NET Core web application dialog with Web Application (Model-View-Controller) selected

Before creating the application, click Change under Authentication and select Individual User Accounts and then Store user accounts in-app:

Individual user accounts with storing user accounts in-app selected

While we won’t be doing much with authentication or identity, this will set us up well to talk about Entity Framework in this article.

ASP .NET Core helpfully defaults to a local database on your development machine when you’re just getting set up. This lets you get started on code quickly and work in a portable environment until you’re ready to switch to a database server else.

Go ahead and create the project.

Launching the Application

Hit Control + F5 or open the Debug menu and select Start without Debugging. The application will begin compiling and launch shortly.

You may see a dialog similar to the following:

SSL Warning dialog

I recommend clicking Yes and then trusting the certificate presented to you. This will allow you to work with https locally.

Once the app compiles, your browser should open and you should see something similar to the following:

The ASP .NET Core 3.0 MVC web application up and running

Congratulations, you have a working ASP .NET Core web application.

Next, let’s click login to create / authenticate your user. You should see a database error page informing you that migrations are needed.

In order to resolve the error we’ll need to go to the Package Manager Console and type Update-Database and hit enter. This will cause the local database to be updated with the tables specified by the identity provider

Package Manager Console running Update-Database

Once that completes, you’ll be able to successfully create and authenticate your user and login.

Adding your First Entity

First, let’s create a class in the Models folder. This can be anything you’d like, but the example here we’ll call it a TestSuite. The TestSuite is a container that will hold TestCase entities later on.

This is what my TestCase class looks like:

Notice the heavy use of attributes. Let’s go over them:

  • Key tells Entity Framework that this is the primary key / identity column in the database table.
  • Required indicates that the field must have a value. This is interpreted at both client-side as well as in Entity Framework’s generated column definitions.
  • HiddenInput is read by the MVC templating engine to not render inputs for the given fields. We’ll see what this means in the next step.
  • Display or DisplayName can be used to customize the way the field is presented to the user in the template.

Next we’ll add a MVC Controller to handle our entity entity and present the user with a view.

We’ll do this by right clicking on the solution explorer and selecting Add > Controller…

This brings up the Add New Scaffolded Item dialog pictured below:

Add New Scaffolded Item with MVC Controller with views, using Entity Framework selected

The process of scaffolding has Visual Studio generate code based on your options and referenced code. In our example, it will generate fields based on our model specified and choices in the Add MVC Controller dialog.

We’ll specify our TestSuite entity in the Model class field. For Data Context, you can specify a new one if you’d like, but there’s not a huge reason not to use the existing ApplicationDbContext class. The remainder of the checkboxes are good to keep checked.

Add MVC Controller dialog with TestSuite specified as the model class

Once you click Add, it will generate everything for you. This could take some time.

A Peek into Entity Framework Migrations

Once the scaffolding operation is complete, take a look at ApplicationDbContext. The scaffolding operation added a line to the context for us:

public DbSet<TestSuite> TestSuite {get; set;}

Because we now have a new entity defined in an EF context, we will need to add an Entity Framework migration for the entity defined.

Do this by running Add-Migration AddedTestCases in package manager console. Note that the name of the migration can be whatever you want, as long as it’s easy for you to understand what operation it represents.

Once this completes, take a look at the class generated by the Add-Migration operation. It should look something like this:

This helps demystify the database migration process. Database migrations are just code that gets executed when the change is applied or reverted.

Once you are ready to apply this database migration, go to Package Manager Console and run Update-Database

Exposing the New View

At around line 25 of _layout.cshtml, add the following code:

Note that asp-controller here refers to the name of the controller, which was named during scaffolding earlier in this tutorial.

This link will allow us to navigate to the new series of views. You should be able to create, read, update, and delete TestSuite entities.

The Test Suites list

While the generated code largely “just works” I did need to make some adjustments. Specifically, on the create page, I removed the created and modified fields.

Additionally, in the create and edit post methods on the TestSuitesController I also set the initial created time and updated the modified time on edit.

Ideally the edit post handler should not take in all properties of the object, but instead only copy over the values exposed in the user interface. This will prevent certain types of attacks and also work better for concurrency if the entities have been changed since the page loaded.

Adding a Second Entity

Now that we have one entity working well, let’s add a TestCase entity and TestCases collection to each TestSuite. This represents a specific test that can be performed.

First, let’s create a simple enum to represent the status of the test case:

Here I’m using the Display attribute to specify how the NotRun value should display if it is ever rendered in a user interface.

Next, let’s define the TestCase:

Here the fields are largely the same as before, but note TestSuite and TestSuiteId. The TestSuiteId is a foreign key relationship field used to store the key of the the TestSuite entity referenced by the TestSuite property.

We’ll also add a collection to the TestSuite class so we can get to the TestCase entities associated with it:

From here, we can go ahead and scaffold the TestCase entity in the same way we did earlier for TestSuite.

Add MVC Controller dialog with TestCase specified as the model class

Once the scaffolding operation is complete, we can create the database migration by using Add-Migration AddedTestCases in Package Manager Console.

Take a look at the generated migration and how it handles foreign keys.

Automatically Migrating Databases

Instead of running Update-Database this time, however, let’s look at a way you can have your application automatically migrate on startup.

Go to Startup.cs and paste in the following method:

Next, at the bottom of the Configure method, add the following statement:


When the server starts up, it will now look at the database and perform any migrations that have not yet been run.

Note that it’s recommended to add a lot more logging and error handling if you’re going to try this in a production application.

View Work for Test Cases

Like before, add a link to the TestCasesController from Layout.cshtml. This will let us look at the default view for Test Cases.

The first thing that stood out to me was that the test case status drop down lists aren’t working. For whatever reason, the default scaffolding doesn’t seem to work well with enums. Thankfully, there’s an easy way around this.

Using the select element, we can customize its asp-items property. This lets us select the source of the items for the drop down. The built-in GetEnumSelectList HTML helper gives us exactly what we want:

This will also respect our display name attribute and display “Not Run” instead of “NotRun”.

The TestCase edit dialog with Status showing options

Next, let’s link to the Test Suite from the Test Case list by replacing the @Html.DisplayFor in the grid with an ActionFor like so:

This tells ASP .NET to generate a link to the Details action on the TestSuites controller and pass in the ID of the suite to view.

Finally, let’s render the list of test cases from a test suite’s details view by adding the following code:

That’s pretty simple code, however it has a problem – it gets a null reference exception when the page is loaded.

What’s going on?

Entity Framework by default will not lazily load collections unless we tell it to. Because of this, Model.TestCases is null and can’t be iterated over.

We can fix this by going into the TestSuitesController‘s Details method. Once there, change the LINQ syntax to be:

Line 2 is particularly important. Include tells Entity Framework to also retrieve the TestCases entity when loading the TestSuite.

With this change, the code should now work properly.

Adding a Web API

Okay, now that we have our entities and user interface, let’s add a Web API controller. We’ll do this via the add controller dialog, but choose an API Controller with Actions, using Entity Framework.

Add New Scaffolded Item dialog with API Controler iwth actions, using Entity Framework selected.

Select the TestCase entity and name your controller TestsController. This name will make sure the controller name doesn’t overlap the MVC controller for the TestSuite entity. Additionally, the default path of this API controller will be api/tests given its name, and this sounds about right to me.

While the generated API methods will let you do a number of things out of the box, let’s add some custom endpoints to look at how you can query using Entity Framework.

Add the following code to your TestsController:

This code defines four new GET endpoints to handle requests. Each one of these will then call to GetTestCasesByStatusAsync and request to filter down to a specific status.

The GetTestCasesByStatus method will then use LINQ’s Where method to filter TestCase entities in a query based on the status parameter passed in.

This is an example of using Entity Framework to manage database objects without needing any SQL. The result is transparent, maintainable, and supports all code completion and compiler safety features in Visual Studio.

We can test this by making an HTTP GET request using Postman or similar to one of our new endpoints when the web application is running.

POSTMAN illustrating a successful HTTP GET call to our new endpoint

Note: If you’d like to learn more about Web API or how to create an API-only project, take a look at my dedicated article on .NET Core APIs.


We managed to get an ASP .NET Core 3.0 web application up and running with user management, database support, and a Web API.

As you can see, scaffolding and HTML helpers powered by attributes are a very powerful way of getting a user interface operational.

Additionally, Entity Framework gives you speed and flexibility in building a database application. It lets you work inside Visual Studio with the assistance of the compiler and IntelliSense. This minimizes the risk of you making a mistake.

Entity Framework migrations and automatic migrations make the database migration process ridiculously simple by baking it into the application itself. This can be an option for teams if supported with proper testing and logging as well as standard database backup and disaster recovery plans.

Ultimately, ASP .NET Core significantly lowers the bar to creating a new application and gives you productivity tools to get up and running quickly. It also has the depth and customization to take an initial concept and serve thousands of customers daily.