Create a web application with ASP.NET Core Web API and Angular

.NET Angular C# Tutorials Create a web application with ASP.NET Core Web API and Angular

ASP.NET Core Web API is a modern framework for building RESTful applications. You can create HTTP services, which may be consumed by any client, including browsers and mobile devices. Angular on the other hand is a component-based framework for building client applications with HTML and TypeScript. In this tutorial, I will show you how to create a web application with ASP.NET Core Web API and Angular. The Angular client will consume the API.

Prerequisites

In the tutorial I am using the following software and framework versions:

Create the Web API Project

Start Visual Studio, click create a new project, search for web api and select the ASP.NET Core Web API project with C#:

Create the ASP.NET Core Web API project
Create the ASP.NET Core Web API project

In this tutorial, we will build a skeleton for a simple recipe blog site. So, set the application name, for instance, Recipes.API:

Create the Angular App, using the CLI - Configure the API project
Configure the API project

In the next step, select the framework – .NET 6.0 (Long-term support), and check Use controllers:

ASP.NET Core Web API - additional information
ASP.NET Core Web API – additional information

Click Create and Visual Studio will initialize your Web API project. It may add some default models and controllers, which you can delete. After all, the project structure should look like this:

Create the Angular App, using the CLI - Web API project structure
Web API project structure

In the launchSettings.json delete the launchBrowser and launchUrl properties. The JSON should look like this:

JSON
{
  "$schema": "https://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:18211",
      "sslPort": 0
    }
  },
  "profiles": {
    "Recipes.API": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "applicationUrl": "http://localhost:5079",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "IIS Express": {
      "commandName": "IISExpress",
      "launchUrl": "weatherforecast",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Create a Controller

Controllers are the entry point of our API. They define all the public methods, which can be consumed by a client. The client in our case will be the Angular app. Right-click on the Controllers folder, and select Add > Controller…

Create the Angular App, using the CLI - Create a new Controller
Create a new Controller

Select MVC Controller – Empty:

Create the Angular App, using the CLI - Add new MVC Empty Controller
Add new MVC Empty Controller

Click Add, enter the name RecipeController and click Add again. Visual Studio will generate an empty Controller with just one method:

C#
using Microsoft.AspNetCore.Mvc;

namespace Recipes.API.Controllers
{
    public class RecipeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

This controller will define recipe related public methods. For instance – get recipes, save, delete, etc.

Create a Service

While the controller contains public methods, called from the client, the service will hold the business logic. The controller should take the responsibility for handling HTTP requests and calling the appropriate service. To distinguish this different logic we will put our services into another project. The first step is to create a new Class Library inside the same solution and name it Recipes.Services. Right-click on the solution and select Add > New Project..., then select Class Library with C#:
Create the Angular App, using the CLI - Add a new Class Library with C# for the Application services
Add a new Class Library with C# for the Application services

Click “Next” and set the Project name to Recipes.Services:

Create the Angular App, using the CLI - Create the Recipes.Services Class Library
Create the Recipes.Services Class Library

Hit Next again, set the framework to .NET 6.0 Long-term support, and click Create:

Create the Angular App, using the CLI - Set framework and create the services class library
Set framework and create the services Class Library

Delete all automatically generated classes inside the Recipes.Services project. This project will contain all application services, for instance – authentication service, user service, etc. In this example, we will define only a recipe service, which will hold the business logic for the recipes. So, add a new folder Recipes, and create a new class RecipeService and an IRecipeService interface inside:

Create the Angular App, using the CLI - RecipeService and IRecipeService interface
RecipeService and IRecipeService interface

Create a simple Model

Similar to the Service creation, let’s put our models inside a separate Class Library called Recipes.Models. Inside we will define one Recipe class, which will hold the recipe’s properties:

Create the Angular App, using the CLI - The Recipes.Models project
The Recipes.Models project

Our recipe model will have 4 properties – Name, Servings, Ingredients and PreparationSteps:

C#
namespace Recipes.Models.Recipe
{
    public class Recipe
    {
        public string Name { get; set; }

        public byte Servings { get; set; }

        public string Ingredients { get; set; }

        public string PreparationSteps { get; set; }
    }
}

Implement Service and Controller’s logic

Now, after we have Controller, Service, and Model defined, let’s write some code, to make them work together. Firstly, let’s define a method GetAllRecipes inside the service interface and implement it in the service. Please note that you will have to add a reference to the Recipes.Models in the Recipes.Services project. The interface method definition:

C#
using Recipes.Models.Recipe;

namespace Recipes.Services.Recipes
{
    public interface IRecipeService
    {
        IEnumerable<Recipe> GetAllRecipes();
    }
}

And, the service method implementation:

C#
using Recipes.Models.Recipe;

namespace Recipes.Services.Recipes
{
    public class RecipeService : IRecipeService
    {
        public IEnumerable<Recipe> GetAllRecipes()
        {
            var products = new List<Recipe>()
            {
                new Recipe()
                {
                    Name = "Cheeseburger",
                    Servings = 1,
                    Ingredients = "1 hamburger roll, 100g freshly ground beef chuck, 1 slice cheddar",
                    PreparationSteps = "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
                },
                new Recipe()
                {
                    Name = "Pizza Margherita",
                    Servings = 1,
                    Ingredients = "100g pizza dough, mozzarella, tomato sauce, basil",
                    PreparationSteps = "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
                },
                new Recipe()
                {
                    Name = "Hot Chocolate",
                    Servings = 1,
                    Ingredients = "milk, milk chocolate, sugar, vanilla extract",
                    PreparationSteps = "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
                }
            };

            return products;
        }
    }
}

The recipes are hard-coded, for the simplicity of the example. In a real-world scenario, you will probably have to fetch them from a database.

The next step is to call the service method inside the controller. We will use Dependency Injection to get our service in the controller. You will also have to add a reference to the Recipes.Services project inside the Recipes.API project. To register a service for a DI go to the Program.cs class and add the following code, just after builder.Services.AddControllers();:

C#
builder.Services.AddScoped<IRecipeService, RecipeService>();

Now, your service is registered, and ready to use with the DI mechanism of .NET. You can learn more about the registration in this article Dependency Injection service lifetimes in .NET.

After that, you can inject the service inside the controller’s constructor, and call GetAllRecipes method inside the Index method of the controller:

C#
using Microsoft.AspNetCore.Mvc;
using Recipes.Models.Recipe;
using Recipes.Services.Recipes;

namespace Recipes.API.Controllers
{
    [Route("[controller]/[action]")]
    public class RecipeController : Controller
    {
        private readonly IRecipeService recipeService;

        public RecipeController(IRecipeService recipeService)
        {
            this.recipeService = recipeService;
        }

        public IEnumerable<Recipe> Index()
        {
            return this.recipeService.GetAllRecipes();
        }
    }
}

We use the [Route("[controller]/[action]")] attribute of the class to define the route of this controller. This pattern will be used to access any controller method.

Configure CORS

To allow our server to receive requests you must set up Cross-Origin Resource Sharing (CORS). This is a mechanism, which allows the server to indicate which domains may request resources. The Angular application we are going to create will run on localhost:4200, so add this code to the Program.cs file, just after the RecipeService registration:

C#
const string AllowSpecificOriginsCors = "AllowSpecificOrigins";
builder.Services.AddCors(options =>
{
    options.AddPolicy(AllowSpecificOriginsCors,
        builder =>
        {
            builder.WithOrigins("http://localhost:4200")
                .AllowAnyMethod();
        });
});

And, after var app = builder.Build(); add:

C#
app.UseCors(AllowSpecificOriginsCors);

This setting will allow our server to receive requests from localhost:4200, where our Angular app will be hosted.

Finally, we have a simple public API method Index, which can be called from our Angular client.

Create the Angular client

Let’s now create the Angular client, which will consume our Web API. Go to the folder where you placed the Recipes.API project and use the Angular CLI to create a new app, by running ng new Recipes.AngularClient. The CLI will ask you if you want to use Angular routing – select yes, and also what kind of stylesheet format you want – select whatever you decide, in my case I choose CSS:

Web application with ASP.NET Core Web API and Angular - Create the Angular App, using the CLI
Create the Angular App, using the CLI

Add bootstrap

Open the Recipes.AngularClient with Visual Studio Code and install bootstrap through npm:

TypeScript
npm install bootstrap

Open Index.html and add the bootstrap style in the head:

HTML
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Recipes.AngularClient</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.css">
</head>

<body>
  <app-root></app-root>
</body>

</html>

Open the angular.json file and include node_modules/bootstrap/dist/css/bootstrap.css in the projects->architect->build->styles array and node_modules/bootstrap/dist/js/bootstrap.js in the projects->architect->build->scripts array.

Add routing

Angular routing is a mechanism that enables navigation between different views or pages of an Angular application. You can find more information in this article.

Firstly, create a new file in the app folder called app.routes.ts:

TypeScript
import { Routes, RouterModule } from '@angular/router'
import { AppComponent } from './app.component';

const appRoutes: Routes = [
    { path: '', component: AppComponent }
]

export const routing = RouterModule.forRoot(appRoutes)

Secondly, in the app.module.ts, register the routes in the imports array:

TypeScript
imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    routing
  ]

Now, you can register new routes inside the app.routes.ts file.

Create Angular service (for API communication)

You will need a service, inside the Angular client, to communicate with the API. Therefore, let’s create api.serice.ts, inside the app/services folder, using Visual Studio Code:

Angular API service location
Angular API service location

Our ApiService will have 2 methods get and post:

TypeScript
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs";

@Injectable()
export class ApiService {
    apiUrl: string = 'http://localhost:5079/'

    constructor(
        private http: HttpClient) {
    }

    get(path: string): Observable<any> {
        return this.http['get'](`${this.apiUrl}${path}`);
    }

    post(path: string, body: any): Observable<any> {
        return this.http['post'](`${this.apiUrl}${path}`, body);
    }
}

As you can see this service is using the build-in Angular HttpClient to make get and post requests to an apiUrl. In our case – this is the URL of the Web API when running on localhost.

And also don’t forget to register your service inside the app.module.ts provider’s array. You will need to register both ApiService and HttpClient. And since the HttpClient is defined inside the HttpClientModule, you must add this module in the imports array as well:

TypeScript
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ApiService } from './services/api.service';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],
  providers: [ApiService, HttpClient],
  bootstrap: [AppComponent]
})
export class AppModule { }

The providers array is a core part of the Angular DI mechanism. You can find more information in this article – Dependency injection in Angular.

Implement the ItemService

The ApiService defines our post and get methods. The ItemService will act as an abstraction between the components and the ApiService. It will call the appropriate ApiService methods, by passing the required payload:

TypeScript
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { ApiService } from "./api.service";

@Injectable()
export class ItemService {
    constructor(
        private apiSerice: ApiService) {
    }

    getItems<T>(itemArgs: ItemArgs): Observable<T> {
        return this.apiSerice.get(`${itemArgs.itemName}/index`) as Observable<T>;
    }
}

interface ItemArgs {
    itemName: string;
}

Register the service inside the providers array of the AppModule:

TypeScript
providers: [ApiService, HttpClient, ItemService]

Add the Recipe model

We will create a new file called recipe.model.ts inside app > models > recipe:

TypeScript
export class Recipe {
    name: string = '';
    servings: number = 0;
    ingredients: string = '';
    preparationSteps: string = '';
}

When we get the recipes from the API, we will map the response to this model.

Load the products in the component

First of all open the app.component.ts and remove the unnecessary code. Let’s add only a header in the template:

HTML
<div class="content" role="main">
  <nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">Navbar</a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
        aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <a class="nav-link active" aria-current="page" routerLink="home">Home</a>
          </li>
        </ul>
      </div>
    </div>
  </nav>
</div>

<router-outlet></router-outlet>

In this template, the <router-outlet> directive is a placeholder used to display the content of the currently selected route. When a user navigates to a different route, the Angular Router replaces the contents of the <router-outlet> with the component that is associated with that route. The routerLink="home" will look inside the app.routes.ts file for a matching component and will navigate on click. This will be our home.comonent.ts. Place it inside app > components > home:

The home.component.ts
The home.component.ts

And also, don’t forget to register the HomeComponent inside the declarations array of the AppModule:

TypeScript
@NgModule({
  declarations: [
    AppComponent,
    HomeComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],
  providers: [ApiService, HttpClient, ItemService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Let’s add the HomeComponent in the app.routes.file:

TypeScript
import { Routes, RouterModule } from '@angular/router'
import { HomeComponent } from './components/home/home.component';

const appRoutes: Routes = [
    { path: '', component: HomeComponent },
    { path: 'home', component: HomeComponent }
]

export const routing = RouterModule.forRoot(appRoutes)

The home.component.ts file is as follows:

TypeScript
import { Component, OnInit } from '@angular/core';
import { Recipe } from 'src/app/models/recipe/recipe.model';
import { ItemService } from 'src/app/services/item.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html'
})
export class HomeComponent implements OnInit {
  public recipes: Recipe[] = [];

  constructor(private itemService: ItemService) { }

  ngOnInit(): void {
    this.itemService.getItems<Recipe[]>({ itemName: "recipe" }).subscribe(result => {
      this.recipes = result;
    })
  }
}

And the home.component.html:

HTML
<ng-container>
    <div class="row">
        <div *ngFor="let recipe of recipes" class="card m-2" style="width: 18rem;">
            <div class="card-body">
                <h5 class="card-title">{{recipe.name}}</h5>
                <h6 class="card-subtitle mb-2 text-muted">{{recipe.servings}}</h6>
                <p class="card-text">{{recipe.preparationSteps}}</p>
            </div>
        </div>
    </div>
</ng-container>

Now, we are ready to test our application.

Run the ASP.NET Core Web API and Angular applications

Firstly, run the Recipes.API from Visual Studio. It should start on http://localhost:5079, as it is configured in the launchSettings.json. Secondly, start your Angular client by typing ng serve in the terminal of Visual Studio Code. It will run by default on http://localhost:4200, and when you access it in the browser:

Web application with ASP.NET Core Web API and Angular - final app
Web application with ASP.NET Core Web API and Angular – final app

As you can see our recipes are visualized inside the home component.

And, if you open the network tab, to inspect server requests, you will see our recipes request and response from the API:

Web application with ASP.NET Core Web API and Angular - recipes API request and response
Web application with ASP.NET Core Web API and Angular – recipes API request and response

Web application with ASP.NET Core Web API and Angular – conclusion and resources

I hope this tutorial was useful and interesting to read. The provided example can be used as a skeleton for a variety of applications. You can visit my GitHub profile to download the example here. Don’t forget to run npm install before running the Angular client.