How to build a web application in the latest .NET platform (.NET 5.0)

As you all know, Microsoft launched the latest version of its .NET platform by unifying all of its .NET flavors/platforms/frameworks. Its actually the 5.0 version of .NETCore 3.1. It will no longer be called .NETCore, but just .NET or with the version .NET 5.0. Microsoft took a huge leap with this release of .NET platform by providing support for all its flavors so it will be the de-facto standard and platform for building high performance platform independent applications for the web, mobile, desktop, IoT. .NET 5.0 will be a current release (CR) as opposed to a Long Term Support (LTS) release, which means that it will be supported for a year from the release (Nov 2020) plus 3 months after the release of the next version (6.0). .NET 6.0 will be an LTS release which will have support for 3 years as usual and is expected to be released Nov. 2021. Happy Thanksgiving! since Microsoft is going to release a new .NET version every November from now!

.NET 5.0 came with a huge list of improvements in terms of performance improvements and new features and fixes. The notable ones are based off of GC (Garbage collection) and ARM64 architecture support, C#9 support and new features that comes along with C#9, improvements to various native libraries eg: Jason serialization, text processing, networking, regular expressions, etc., improved support for single file deployments/containerization/CI/CD, JIT (Just in time) compilation, trimming etc. For more details, look at .NET 5.0 Performance Improvements.

With all that said about .NET, now lets a look at how to build a simple MVC based web application in .NET 5.0. This a simple guide to help the beginner to get started. This tutorial assumes that you have some working knowledge of C# programming language, Linq and razor.

Step 1: Create Project in Visual Studio (VS 2019 16.8.1 required)

.NET 5.0 needs VS 16.8.1, so use VS installer to upgrade your VS if you haven’t already. Also go to .NET 5.0 SDK download to download and install the needed SDK. As shown below, Create New Project from the menu, select ASP.NET Core Web Application click ‘Next’. Then give appropriate project name and click ‘Create’. Next, as shown below, select MVC as the template for the ASP.NET web application. Select the latest version of  ASP.NETCore 5.0.

Now focus on the top right corner for Authentication. Click on ‘Change’. It will present a dialog box with 4 distinct options for you to choose from depending on the needs of your application. Many times, you would need a user registration and account management feature built into your application. In that case, you would choose the second option that says ‘Individual User Accounts’. In doing so, you would be installing .NET UserIdentity and Entity Framework to manage the user accounts registration and management. The cool thing is that it automatically installs Entity Framework for you since UserIdentity needs it anyway. Now, if your app. need to use windows authentication (as inside an intranet), then choose the last option that says ‘Windows Authentication’. Remember, using this option, you will have to deal with user management and you will have to install and configure Entity Framework by yourself if you need to. In our example, we will use the 4th option to set a scenario where you would be using this app. with an admin module used within an intranet and part of it exposed outside. This will also help those who want to know how to install Entity Framework and link it to the project.

Step 2: Initial Project Setup and about the web application

For purposes of this tutorial, we will be building a simple Contact Card application that allows the user to manage contacts.

User Story: The application will allow user to add, edit and delete contacts and allows user to view a list of available contacts so that he/she could effectively manage his contacts.
Acceptance Criteria: Should have basic validations on the Add/Edit forms. The system should only be accessible to authorized users within an intranet over LDAP account and the contact list view should be pluggable to outside world for general public view.

Basic Project Setups: As you can see VS creates the basic MVC structure for you as shown below –

For experienced developer above structure is self-explanatory. For beginners, I will try to briefly explain what it means –

Startup.cs file is somewhat similar to the Global.asax file in .NET Framework in the way that it is the entry point of the application. But it is significantly different in design and the way it configures the request pipeline that handles the requests in/out of the application via dependency injection. For eg: see how Configuration object is injected via Constructor overloading. Similarly Services object is injected via ConfigureServices method and we register various services that will be started and used throughout the application. Note the Route.config in older framework is also defined in the Startup.cs. Startup.cs is further invoked via Program.cs. If you are interested to read more – https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-3.1.

Appsettings.json is the application settings file where you want to store and define common configuration variables which you would like to access throughout the application.
Dependencies shows all the external packages installed within your project. If you expand and see, you will notice that EntityFramework package is missing since we choose Windows Authentication (as mentioned in a section above).

Models holds all your application DB Model

Views holds all the views for your application

Controllers holds all your application controllers

Scripts holds your javascipt/jquery stuff

wwwroot holds the core css, js/jquery files. VS includes the latest bootstrap version based on the VS version. You can override this by copying your own taste of bootstrap themes or latest bootstrap version downloaded from (https://getbootstrap.com/docs/4.5/getting-started/download/). As of this writing, the latest version is 4.5.2.

For example, if you want to use custom themes for your CSS, there is a useful site for it – www.bootswatch.com. I like Lumen theme as it provides the latest web app. facelift for your entire site. So download both min and normal css files as shown below and store it under ‘wwwroot/lib/bootstrap/dist/css/’ folder (take backup of original bootstrap css files in case you need it).

bootswatch
bootswatch

Step 3: Create Models (First!) via Code First approach

Code First with Entity Framework is the most recommended approach to cleanly design, build and maintain an ASP.NET web application. This statement is from my personal experience and from the collective recommendation and experience from the industry. Basically, this allows you to think and define your entities first via a logical model design. Then wire it up to Entity Framework layer and use Migrations feature to automatically document and version the DB creation and subsequent DDL updates. By doing this, you can easily apply and revert a particular version of your DB structure from a point of time onto any desired environment at any time. What this means is you don’t have to worry about managing your DB creation scripts and its versioning and the related headaches of applying it to a specific environment while you move your application across various environments. Ok, now let’s get our hands dirty looking at how we do this and create our first cut of Contacts Cards database.

3.1 Create ContactCard.cs

So the first step is to create the Model Class. As shown below, create new class under the ‘Models’ folder –

Name the class file as ContactCard.cs and click ‘Add’. You will see the new class file created under the ‘Models’ folder.

By this time, you should have done your homework on identifying what information you need to manage within your Contact entity viz. Name, Email, Phone etc. You define this information as properties within the Model class as below –

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace ContactCards.Models
{
public class ContactCard
{
public int Id { get; set; }

[DisplayName("First Name")]
[StringLength(50)]
public string FirstName { get; set; }

[DisplayName("Last Name")]
[StringLength(50)]
public string LastName { get; set; }

[DisplayName("Middle Initial")]
[StringLength(1)]
public string MiddleInitial { get; set; }

[DisplayName("Email")]
[Required(ErrorMessage = "The email address is required")]
[EmailAddress(ErrorMessage = "Invalid Email Address")]
public string Email { get; set; }

[DisplayName("Phone")]
[Required(ErrorMessage = "The Phone is required")]
public string Phone { get; set; }

[DisplayName("Address")]
[Required(ErrorMessage = "The Address is required")]
public string Address1 { get; set; }

[DisplayName("Address 2")]
public string Address2 { get; set; }

[DisplayName("City")]
[Required(ErrorMessage = "The City is required")]
public string City { get; set; }

[DisplayName("State")]
[Required(ErrorMessage = "The State is required")]
[StringLength(2)]
public string State { get; set; }

[DisplayName("Zip Code")]
[Required(ErrorMessage = "The Zip Code is Required")]
[RegularExpression(@"^\d{5}(-\d{4})?$", ErrorMessage = "Invalid Zip")]
public string Zipcode { get; set; }
}
}

Highlights of the Model class – As you can see, we have removed unnecessary include from the top and just retained the ones that we really use. Also, we have decorated Model properties with DataAnnotation and thus included the required directives on top. We have used at least 5 DataAnnotation attributes such as DisplayName, Required, StringLength etc. If you are interested, look them up to learn more. For eg: see how handy is to use DataAnnotation for Email and Regular expression check for Zipcode which will save a lot of coding at front end across the application.

3.2 Kick Off Migration and cut the first version of Database!

Ok, now it’s time to use Entity Framework, specifically the Core version, to create our first cut of Database using Migrations. Microsoft has enhanced the Core 5.0 version to be light weight, more extensible and open-sourced. As mentioned in an above section, since we opted to use Windows Authentication, we don’t have Entity Framework installed by default. So we will use Nuget Package manager to install it and we will go from there as shown below –

Nuget Package Manager

Note that we are installing various components of Entity Framework Core 5.0 to suit our needs. Ensure that you install all packages as shown below –

After successful installation, you should see the below five packages under your project references –

Once Entity Framework is installed, you can now go ahead and create your Physical Database. First step is to create a DBContext file that defines the Dbsets which links EntityFramework Dbsets to the actual physical database. So, create a class ContactCardContext.cs (you can call it anything, but standard practice is to call it something related to the core DB with the word ‘Context’ attached to it) under the Model folder and add below code to define the DBset –

using Microsoft.EntityFrameworkCore;

namespace ContactCardsCore.Models
{
public class ContactCardContext : DbContext
{
public ContactCardContext(DbContextOptions options)
: base(options)
{
}

public virtual DbSet<ContactCard> ContactCards { get; set; }

}
}

As you see above, our custom DbContext file derives from the DbContext class that hooks on to the Entity Framework Core objects to interact with the database objects. There is couple extra steps that you need to do to before you can go ahead and start creating the database.

1. Add below to appsettings.json to denote default connection string

"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContactCardCore;Trusted_Connection=True;MultipleActiveResultSets=true"
}

2. Add below code to StartUp.cs to register the DbContext as a service with the DB connection string details –

services.AddDbContext<ContactCardContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));

The next step is to go ahead and create the physical database. This process is called ‘Migrations’ where you will kick start your DB versioning and your physical DB creation or update process. With Entity Framework Core, you don’t have to enable migrations for your project, you can start from adding a migration point and then create DB.

Add Migration – In this step, we add the Migration point or the DB update version which tells Entity Framework Core to create DDL statements based off of the current Model classes in Model folder. So, Issue command ‘Add-Migration “FirstCut”‘ from PM console (‘FirstCut’ is just a name, you can give any name) and you will see below results –

Last Step in DB creation – Create Database – Now, the last step is to create database. For this, we use ‘Update-Database’ command at PM console as shown below –

Issuing above command tells Entity Framework Core to look into the versioned DB update file 202009xxxx and execute the ‘down’ and ‘up’ command – basically the DDL create table statement which was created from ContactCard.cs. On successful creation, you can verify the DB by going to the SqlServer Object Explorer as shown in the inner snap on the above image.

Step 4: Create Controllers and Views

Now that we have laid out the basic data structure and its physical design, we will put together views and controllers to build the UI to enable data management and the basic reporting of the contacts list. I recommend that you build the Views and Controllers from scratch so you learn the foundation. You would create the Controller/s first based on the data management and reporting needs. For eg: in our case, we would need the ability to Add/Edit/Delete Contacts by an internal admin and also the ability to output the contacts list to the public. So, we start with creating a Controller class whose name goes by sController.cs. So our controller will be named as ContactCardsController.cs.

In MVC land, we generally correlate the basic data management functions in the same literal sense when we name the controller methods and the corresponding views. The ‘index’ method is generally the root of the controller as defined in your Default route (Startup.cs). The corresponding view will be named as Index.cshtml and placed under the Views/ContactCards folder. Note that the View subfolder is named according to the Controller name minus the ‘Controller’ word. We will be using razor front end scripting for creating our views. To manage the Add, Edit, Delete features, we will create Create, Edit, Delete controller methods and corresponding Views respectively.

So, we will end up creating below controller and the corresponding code to allow Create, Edit, Delete of contacts and the basic listing of the contact. Please do your home-work learning intermediate level C# programming, Linq and razor coding so you understand its usage in the controller methods and the C# code blocks within razor.

4.1 ContactCardsController.cs

ContactCardController
ContactCardController

Right click on ‘Controllers’ folder and select new class and create the controller class. Below is a sample Controller code to allow Create, Edit, Delete and Contact list operations.

using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ContactCardsCore.Models;
using Microsoft.AspNetCore.Authorization;

namespace ContactCardsCore.Controllers
{
public class ContactCardsController : Controller
{
private readonly ContactCardContext _context;

public ContactCardsController(ContactCardContext context)
{
_context = context;
}

// GET: ContactCards
[AllowAnonymous]
public async Task Index()
{
return View(await _context.ContactCards.ToListAsync());
}

// GET: ContactCards/Create
public IActionResult Create()
{
return View();
}

// POST: ContactCards/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task Create(ContactCard contactCard)
{
if (ModelState.IsValid)
{
_context.Add(contactCard);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(contactCard);
}

// GET: ContactCards/Edit/x
public async Task Edit(int? id)
{
if (id == null)
{
return NotFound();
}

var contactCard = await _context.ContactCards.FindAsync(id);
if (contactCard == null)
{
return NotFound();
}
return View(contactCard);
}

// POST: ContactCards/Edit/x
[HttpPost]
[ValidateAntiForgeryToken]
public async Task Edit(int id, [Bind("Id,FirstName,LastName,MiddleInitial,Email,Phone,Address1,Address2,City,State,Zipcode")] ContactCard contactCard)
{
if (id != contactCard.Id)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
_context.Update(contactCard);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ContactCardExists(contactCard.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(contactCard);
}

// GET: ContactCards/Delete/x
public async Task Delete(int? id)
{
if (id == null)
{
return NotFound();
}

var contactCard = await _context.ContactCards
.FirstOrDefaultAsync(m => m.Id == id);
if (contactCard == null)
{
return NotFound();
}

return View(contactCard);
}

// POST: ContactCards/Delete/x
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task DeleteConfirmed(int id)
{
var contactCard = await _context.ContactCards.FindAsync(id);
_context.ContactCards.Remove(contactCard);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}

private bool ContactCardExists(int id)
{
return _context.ContactCards.Any(e => e.Id == id);
}
}
}

Note that we have only used the required using directives to deal with MVC actions. We created the ‘db’ private readonly property to inject the DbContext to tap into the Database via Entity Framework Core. We use async/await methods just to serve its popular use in .NETCore. It is not necessary to use it. It is relevant in application scaling and when you want to make sure that you make effective resource utilization if you are dealing with a loaded application.

The Index() method creates a list of current contacts and returns to the index.cshtml View to output the list. Note that this method is decorated as ‘AllowAnonymous’ which will bypass authorization and allows to expose the data list outside the intranet.

The ‘Create’ method is polymorphic and the method with empty parameter ( Create() ) allows for the display of the create.cshtml View (the form that captures Contacts data) and the method signature which takes in the submitted ContactsCards model ( Create(ContactCard contactCard)
), allows for the validation of the incoming Model data and final push into the Database.

The ‘Edit’ method is polymorphic as well and the method with id parameter ( Edit(int? id) ) allows for the display of the edit.cshtml View (the form that displays edit form filled with Contacts data) and the method signature which takes in the submitted ContactsCards model ( Edit(ContactCard contactCard) ), allows for the validation of the incoming edited Model data and its final push into the Database.

The Delete methods works in the same fashion as they are invoked with the ‘id’ being passed to indicate which record to delete. There is a delete confirmation page that gets displayed before the final DeleteConfirmed(int id) method is called.

4.2 Views or the UI

Notice that with latest versions of .NETCore, html tag helpers (eg: asp-for as used with the label tag) have been heavily used as opposed to html helper functions (eg: @Html.ActionLink) provided by razor as in older .NETCore versions and .NETFramework. It is your personal preference to choose one over the other. Note that tag helpers are more robust in the way that they allow you to use html and javascript more freely and not being restricted to the extensibility of html helper function in scenarios where you want to use mix-match of html, stylesheet and javascript. The other matter of personal preference is use of ViewBag Vs ViewData to pull in controller data into views. As you will see .NETCore prefers ViewData[], but again ViewBag. will just work.

Before we talk about views, let’s do some basic house-keeping on the application home page and main menu on the top that VS created for you by default. If you run the project, you will see a default home page that gets routed via the Home controller and it’s View under Views/Home/index.cshtml. HomeController.cs is automatically created for you and you may keep it, remodel it etc. For eg: you can remodel it as your application home page with some cool graphics, sliders or custom menu and content if you are building a web-site. You can keep the About Us and Contact Us Views as you need it. For our scenario, we decide to keep the home page as the contacts list page and we don’t need the about us and contact us page.

4.2.1 Here is how we do above UI changes –

Change the top menu to fit your application need – Edit Views/Shared/_Layout.cshtml with the following razor code –

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] – Contact Cards</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">ContactCardsCore</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <p class="nav navbar-text navbar-right">Hello, @User.Identity.Name!</p>
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        @*<li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                        </li>*@
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2020 - Contact Cards Application
        </div>
    </footer>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>

Note the yellow highlighted areas where we modified the page title, menu and footer display to fit our needs. Ok, as you Cntrl+F5 now, your home page should look like below –

Contact Card Home Page
Contact Card Home Page

We will now see the 4 main Views that we will be creating to enable various functions for the web application. Towards the end of this tutorial, we will see how to link ‘Home’ link to the Contacts Index View to complete the wiring.

4.2.2 Main Views

Below are the main views that we will be creating to deal with the basic functionality of our application –

Views
Views

Contacts List Index Page (Views/ContactCards/Index.cshtml)

The contact list view is created under Views/ContactCards folder as Index.cshtml. As we saw before, Index() method within ContactCardsController.cs spits out the contacts list on to this view. Note the naming conventions and how MVC makes sure it orchestrates the process flow from Controller to Model and back to the View. Below is the razor code for contacts list Index view –

@model IEnumerable<ContactCardsCore.Models.ContactCard>

@{
    ViewData["Title"] = "Contacts List";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h3>Contacts List</h3>

<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.MiddleInitial)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Email)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Phone)
            </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.Zipcode)
            </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.MiddleInitial)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Email)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Phone)
            </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.Zipcode)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Note that on the top, we include the base Model which will be used to extract data from, which is the ContactCard model. With your C# background, you should know that IEnumerable creates an Enumerable list of ContactCard so we can iterate over it and print the list. We use basic razor functions such as DisplayNameFor and DisplayFor to display the Model field name (this will be overridden by the DataAnnotation attribute in Model class if you have used it to decorate the DB field) and the corresponding field value from DB. Also note the basic bootstrap table css to make it look more professional and responsive. The ‘Edit’ and ‘Delete’ links are wired to the respective Views to enable Edit and Delete operations (links not in snap since we haven’t entered any records yet). If you run your app now and go to /ContactCards the index page should look like below –

Index Page
Index Page

Create View (Views/ContactCards/Create.cshtml)

The create view is created under Views/ContactCards folder as Create.cshtml. As we saw before, Create() method within ContactCardsController.cs displays this view which is basically the Create form to capture contacts input data. Below is the razor code for Create view –

@model ContactCardsCore.Models.ContactCard

@{
    ViewData["Title"] = "Create Contact";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h3>Create Contact</h3>

<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="FirstName" class="control-label"></label>
                <input asp-for="FirstName" class="form-control" />
                <span asp-validation-for="FirstName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="LastName" class="control-label"></label>
                <input asp-for="LastName" class="form-control" />
                <span asp-validation-for="LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="MiddleInitial" class="control-label"></label>
                <input asp-for="MiddleInitial" class="form-control" />
                <span asp-validation-for="MiddleInitial" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Email" class="control-label"></label>
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Phone" class="control-label"></label>
                <input asp-for="Phone" class="form-control" />
                <span asp-validation-for="Phone" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Address1" class="control-label"></label>
                <input asp-for="Address1" class="form-control" />
                <span asp-validation-for="Address1" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Address2" class="control-label"></label>
                <input asp-for="Address2" class="form-control" />
                <span asp-validation-for="Address2" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="City" class="control-label"></label>
                <input asp-for="City" class="form-control" />
                <span asp-validation-for="City" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="State" class="control-label"></label>
                <input asp-for="State" class="form-control" />
                <span asp-validation-for="State" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Zipcode" class="control-label"></label>
                <input asp-for="Zipcode" class="form-control" />
                <span asp-validation-for="Zipcode" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Some notable things are the model that is included on top and various html tag helpers to bind labels etc to the Model properties and display the validation summary, html label, objects, links etc. If you click on the ‘Create New’ link on the Contacts List page, it should display below form –

Create Form
Create Form

Once user enters data and clicks ‘CREATE’ button, the form gets posted onto Create(ContactCard contactCard) method within ContactCardsController and data gets processed there. If there is a validation error, it fails on if (ModelState.IsValid) and the method returns back to the Create View with the raised Model error.

See below for an eg: where the client side validation is triggered by the view html –

Create Form front end validation
Create Form front end validation

This gets triggered from the asp-validation-for razor function which already interrogated the Model DataAnnotation and determined the client side validation and error message for it. In this particular case, it’s an invalid email or zipcode.

Below is an example for an error that is raised within the Controller after data is posted –

Server Side and Front End Validation
Server Side and Front End Validation

As you can see the required fields are checked before controller method invokes the DB functions to finally insert the data. This check is performed by the ‘if (ModelState.IsValid) ‘ check.

If you enter valid data as shown below,

Create Form Filled
Create Form Filled

…. data will be inserted into DB and the Controller method will redirect you to the Contacts list Index page as show below –

Contact List Page
Contact List Page

Edit View (Views/ContactCards/Edit.cshtml)

The edit view is created under Views/ContactCards folder as Edit.cshtml. As we saw before, Edit(int? id) method within ContactCardsController.cs displays this view which is basically the Edit form to edit contacts data. Note that Edit method need to be invoked with an id that points to the specific DB record.

Below is the razor code for Edit view –

@model ContactCardsCore.Models.ContactCard

@{
    ViewData["Title"] = "Edit Contact";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h3>Edit Contact</h3>

<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="FirstName" class="control-label"></label>
                <input asp-for="FirstName" class="form-control" />
                <span asp-validation-for="FirstName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="LastName" class="control-label"></label>
                <input asp-for="LastName" class="form-control" />
                <span asp-validation-for="LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="MiddleInitial" class="control-label"></label>
                <input asp-for="MiddleInitial" class="form-control" />
                <span asp-validation-for="MiddleInitial" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Email" class="control-label"></label>
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Phone" class="control-label"></label>
                <input asp-for="Phone" class="form-control" />
                <span asp-validation-for="Phone" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Address1" class="control-label"></label>
                <input asp-for="Address1" class="form-control" />
                <span asp-validation-for="Address1" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Address2" class="control-label"></label>
                <input asp-for="Address2" class="form-control" />
                <span asp-validation-for="Address2" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="City" class="control-label"></label>
                <input asp-for="City" class="form-control" />
                <span asp-validation-for="City" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="State" class="control-label"></label>
                <input asp-for="State" class="form-control" />
                <span asp-validation-for="State" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Zipcode" class="control-label"></label>
                <input asp-for="Zipcode" class="form-control" />
                <span asp-validation-for="Zipcode" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}

You will see that the razor code for the Edit form looks very similar to the Create view with one exception of the hidden field that is added on the Edit form which will be passed to the Edit controller to link it back to the DB row where the Edit will be posted to.

Clicking on the ‘Edit’ link on the Contacts list Index page, below Edit form is loaded –

Edit Form
Edit Form

Once you submit, the posted data is saved against the linked ‘id’ and the controller method re-directs you back to the contacts list page.

Contact List After Edit
Contact List After Edit

Validations and such works similar to the Create function.

Delete View (Views/ContactCards/Delete.cshtml)

The delete view is created under Views/ContactCards folder as Delete.cshtml. Below is the razor code for Delete view –

@model ContactCardsCore.Models.ContactCard

@{
    ViewData["Title"] = "Delete Contact";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h3>Are you sure you want to delete this?</h3>
<div>
    <hr />
    <dl class="row">
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.FirstName)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.FirstName)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.LastName)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.LastName)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.MiddleInitial)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.MiddleInitial)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.Email)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.Email)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.Phone)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.Phone)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.Address1)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.Address1)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.Address2)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.Address2)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.City)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.City)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.State)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.State)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.Zipcode)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.Zipcode)
        </dd>
    </dl>
    
    <form asp-action="Delete">
        <input type="hidden" asp-for="Id" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-action="Index">Back to List</a>
    </form>
</div>

Delete view is invoked by clicking on the ‘Delete’ link on the contacts list page. This will pass in the id of the record onto Delete(int? id) method which further displays a confirmation page (above razor code) with details of the contact and asking user to confirm Delete as shown below –

Delete Confirm Form
Delete Confirm Form

Once user press ‘Delete’ button, control is passed to DeleteConfirmed(int id) where the record is deleted invoking the Linq query _context.ContactCards.Remove(contactCard)

Link Home page and ‘Home’ menu item on top menu to Contacts list Index page

As a final step in building the web application, we will replace the current home page with the contacts list index page and will do the same for the ‘Home’ menu item on the top.

To do this, just replace the code within Index() method of HomeController.cs with below code –

return RedirectToAction("Index", "ContactCards");

Basically, we are redirecting the Action result to the Index method of ContactCards controller.

That’s it! Your web application to manage Contact Cards is complete. Your home page should now load as below –

Final Contacts List Page
Final Contacts List Page