C# Corner

ASP.NET Core: Learning the Ropes, Part 1

ASP.NET Core is the successor to ASP.NET MVC and ASP.NET Web API and was first introduced in 2016. This means you can create a new MVC Web application, Web API service or both using .NET Core. One of the main benefits of ASP.NET Core is that it will run on macOS, Linux and Windows. Today I'll be covering how to get started with ASP.NET Core to create an MVC application. I'll be using Visual Studio 2017 Community Edition version 15.8.6 for Windows and the newly released ASP.NET Core 2.1.5.

To get started download the latest .NET Core SDK. As of this writing the latest version is SDK v2.1.403. You should see Figure 1 once you've completed the installation.

Completed .NET Core SDK Installation
[Click on image for larger view.] Figure 1. Completed .NET Core SDK Installation

Now that you have the latest SDK installed, open up Visual Studio and create a new ASP.NET Core Application as seen in Figure 2.

Creating New ASP.NET Core Web Application
[Click on image for larger view.] Figure 2. Creating New ASP.NET Core Web Application

On the next screen select the Model View Controller option as seen in Figure 3.

ASP.NET Core MVC Project Selection
[Click on image for larger view.] Figure 3. ASP.NET Core MVC Project Selection

Now we are going to force Visual Studio to use the latest version of the Microsoft.AspNetCore.App by editing the project file (Figure 4); without this change you will be stuck at version 2.1.1.

Edit Project File
[Click on image for larger view.] Figure 4. Edit Project File

Now add the Version attribute flag to the Microsoft.AspNetCore.App line:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.5" />
    <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />
  </ItemGroup>

</Project>

You should now be able to run the application and see a basic page with a menu and a rotator on it as seen in Figure 5.

Starter Application
[Click on image for larger view.] Figure 5. Starter Application

Now it's time to get dive into some development. I'll walk you through how to create a simple page that contains a bootstrap-styled table of contacts retrieved using a stub repository and dependency injection. In part 2 I'll show you how to use Entity Framework Core to create, read, update and delete a contact.

The first thing we need is a model class for a single contact. Add a new class named Contact to the Models directory. Our Contact class is pretty basic and will store an ID, first name, last name and e-mail address:

namespace VSMASPCoreApp.Models
{
  public class Contact
  {
    public int ContactId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string EmailAddress { get; set; }
  }
}

Now that we can store a contact we need a means of retrieving a contact by its ID or all of the contacts. To do this we'll create a contact repository interface and stub implementation. Create a new directory named Repositories. Next create a new interface in the Repositories folder named IContactRepository. First add a using statement for the Models namespace:

using VSMASPCoreApp.Models;

Then add a method named GetContactById that takes an ID and returns a Contact:

Contact GetContactById(int id);

Lastly add a method named GetContacts that returns a collection of contacts:

IEnumerable<Contact> GetContacts();

You should now have a completed IContactRepository:

using System.Collections.Generic;
using VSMASPCoreApp.Models;

namespace VSMASPCoreApp.Repositories
{
  public interface IContactRepository
  {
    Contact GetContactById(int id);
    IEnumerable<Contact> GetContacts();
  }
}

Now we are going to add a mock contact repository that implements the IContactRepository. Create a new class named MockContactRepository in the Repositories folder. The MockContactRepository is going to contain a hard-coded list of contacts rather than hitting a database. In the constructor I create a hard-coded list of contacts. In the GetContactById method I find a single contact by its ID. For the GetContacts method I return the contacts collection sorted by last name and then first name ascending. See Listing 1 for the complete MockContactRepository implementation.

Listing 1: MockContactRepository.cs
using System.Collections.Generic;
using System.Linq;
using VSMASPCoreApp.Models;

namespace VSMASPCoreApp.Repositories
{
  public class MockContactRepository : IContactRepository
  {
    private List<Contact> _contacts;

    public MockContactRepository()
    {
      _contacts = new List
      {
        new Contact
        {
          ContactId = 1,
          FirstName = "Phillip",
          LastName = "Fry",
          EmailAddress = "[email protected]"
        },
        new Contact
        {
          ContactId = 2,
          FirstName = "Hubert",
          LastName = "Farnsworth",
          EmailAddress = "[email protected]"
        },
        new Contact
        {
          ContactId = 3,
          FirstName = "Amy",
          LastName = "Wong",
          EmailAddress = "[email protected]"
        },
        new Contact
        {
          ContactId = 4,
          FirstName = "Turanga",
          LastName = "Leela",
          EmailAddress = "[email protected]"
        },
        new Contact
        {
          ContactId = 5,
          FirstName = "Bender",
          LastName = "Rodriguez",
          EmailAddress = "[email protected]"
        }
      };
    }

    public Contact GetContactById(int id)
    {
      return _contacts.SingleOrDefault(x => x.ContactId == id);
    }

    public IEnumerable GetContacts()
    {
      return _contacts.OrderBy(x => x.LastName).ThenBy(x => x.FirstName);
    }
  }
}

Now that we have a contact repository interface and mock implementation, we need to tell ASP.NET Core that when we ask for an ICotanctRepository we want to use the MockContactRepository implementation. This is known as dependency injection and it's supported out of the box in ASP.NET Core. Open the Startup.cs file and add a using statement for the Repositories namespace:

using VSMASPCoreApp.Repositories;

Next we update the ConfigureServices method to tell ASP.NET Core that we want return a MockContactRepository whenever we ask for a IContactRepository:

services.AddTransient<IContactRepository, MockContactRepository>();
See Listing 2 for the fully updated Startup code.

Listing 2: Startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using VSMASPCoreApp.Repositories;

namespace VSMASPCoreApp
{
  public class Startup
  {
    public Startup(IConfiguration configuration)
    {
      Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
      services.Configure(options =>
      {
        // This lambda determines whether user consent for non-essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
      });

      services.AddTransient<IContactRepository, MockContactRepository>();

      services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
      if (env.IsDevelopment())
      {
        app.UseDeveloperExceptionPage();
      }
      else
      {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
      }

      app.UseHttpsRedirection();
      app.UseStaticFiles();
      app.UseCookiePolicy();

      app.UseMvc(routes =>
      {
        routes.MapRoute(
          name: "default",
          template: "{controller=Home}/{action=Index}/{id?}");
      });
    }
  }
}

Now that we have the model layer handled for our simple application we are going to dive into the controller and view code. Create a new controller named ContactController in the Controllers folder as seen in Figure 6.

Creating Contact Controller Class
[Click on image for larger view.] Figure 6. Creating Contact Controller Class

First add a using statement for the Repositories namespace to the controller:

using VSMASPCoreApp.Repositories;

Then create an IContactRepository private member that will be injected in the constructor:

private readonly IContactRepository _contactRepository;

Next update the constructor to set the _contactRepository to the passed in IContactRepository:

public ContactController(IContactRepository contactRepository)
{
  _contactRepository = contactRepository;
}

Lastly update the Index method to use the _contactRepository to get all of the contacts and pass it to the view. See Listing 3 for the completed contact controller.

Listing 3: ContactController.cs

using Microsoft.AspNetCore.Mvc;
using VSMASPCoreApp.Repositories;

namespace VSMASPCoreApp.Controllers
{
  public class ContactController : Controller
  {
    private readonly IContactRepository _contactRepository;

    public ContactController(IContactRepository contactRepository)
    {
      _contactRepository = contactRepository;
    }

    public IActionResult Index()
    {
      var contacts = _contactRepository.GetContacts();
      return View(contacts);
    }
  }
}
public IActionResult Index()
 {
   var contacts = _contactRepository.GetContacts();
   return View(contacts);
 }

Now we need to add the view that has a model of a collection of contacts. First create a new folder named Contact under the Views folder. Then create a new Razor View named Index under the new Contact folder as seen in Figure 7.

Creating Empty Contact Index Razor View
[Click on image for larger view.] Figure 7. Creating Empty Contact Index Razor View

For the view I am simply creating a bootstrap styled table of all of the contacts. I set the model to be an IEnumerable as is passed from the Index contact controller action. Then I loop over all of the contacts and add rows to a table. See Listing 4 for the complete Index view.

Listing 4: Contact Index.cshtml View

@model IEnumerable<Contact>
<table class="table-striped table-responsive table-bordered">
    <thead>
        <tr>
            <th>First</th>
            <th>Last</th>
            <th>Email</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var contact in Model)
        {
        <tr>
            <td>@contact.FirstName</td>
            <td>@contact.LastName</td>
            <td>@contact.EmailAddress</td>
        </tr>
        }
    </tbody>
</table>

The final step is to update the Contact link in the top menu to link to our new contact controller. Open up the Views\Shared\_Layout.cshtml Razor view and find line 35:

<li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>

Simply update the asp-controller to be Contact and the asp-action to be Index instead:

<li><a asp-area="" asp-controller="Contact" asp-action="Index">Contact</a></li>

See Listing 5 for the fully updated _Layout.cshtml file.

Listing 5: _Layout.cshtml Menu Updated

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>@ViewData["Title"] - VSMASPCoreApp</title>

  <environment include="Development">
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
  </environment>
  <environment exclude="Development">
    <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
        asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
        asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
    <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
  </environment>
</head>
<body>
  <nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
      <div class="navbar-header">
        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">VSMASPCoreApp</a>
      </div>
      <div class="navbar-collapse collapse">
        <ul class="nav navbar-nav">
          <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
          <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
          <li><a asp-area="" asp-controller="Contact" asp-action="Index">Contact</a></li>
        </ul>
      </div>
    </div>
  </nav>

  <partial name="_CookieConsentPartial" />

  <div class="container body-content">
    @RenderBody()
    <hr />
    <footer>
      <p>© 2018 - VSMASPCoreApp</p>
    </footer>
  </div>

  <environment include="Development">
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
  </environment>
  <environment exclude="Development">
    <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
        asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
        asp-fallback-test="window.jQuery"
        crossorigin="anonymous"
        integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT">
    </script>
    <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
        asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
        asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
        crossorigin="anonymous"
        integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
    </script>
    <script src="~/js/site.min.js" asp-append-version="true"></script>
  </environment>

  @RenderSection("Scripts", required: false)
</body>
</html>

You should now be able to run the application and click on the Contact menu link and see a list of contacts in a nicely bootstrap-styled table as seen in Figure 8.

Finished Application
[Click on image for larger view.] Figure 8. Finished Application

Today I've covered how to use ASP.NET Core 2.1.5 to create an MVC application that creates a table of contacts. Stay tuned for part 2 where I'll go over how to add CRUD functionality to the contacts page using Entity Framework Core using a real database!

About the Author

Eric Vogel is a Senior Software Developer for Red Cedar Solutions Group in Okemos, Michigan. He is the president of the Greater Lansing User Group for .NET. Eric enjoys learning about software architecture and craftsmanship, and is always looking for ways to create more robust and testable applications. Contact him at [email protected].

comments powered by Disqus

Featured

  • Build Your First AI Applications with Local AI

    "AI right now feels like a vast space which can be hard to jump into," says Craig Loewen, a senior product manager at Microsoft who is helping devs unsure about making that first daunting leap.

  • On Blazor Component Reusability - From Day 0

    "We want to try to design from Day One, even Day Zero, with reusability in mind," says Blazor expert Allen Conway in imparting his expertise to an audience of hundreds in an online tech event on Tuesday.

  • Decision Tree Regression from Scratch Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of decision tree regression using the C# language. Unlike most implementations, this one does not use recursion or pointers, which makes the code easy to understand and modify.

  • Visual Studio's AI Future: Copilot .NET Upgrades and More

    At this week's Microsoft Ignite conference, the Visual Studio team showed off a future AI-powered IDE that will leverage GitHub Copilot for legacy app .NET upgrades, along with several more cutting-edge features.

  • PowerShell Gets AI-ified in 'AI Shell' Preview

    Eschewing the term "Copilot," Microsoft introduced a new AI-powered tool for PowerShell called "AI Shell," available in preview.

Subscribe on YouTube