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:
- Visual Studio 2022
- Visual Studio Code v1.76
- .NET 6.0
- Angular 14
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#:
In this tutorial, we will build a skeleton for a simple recipe blog site. So, set the application name, for instance, “Recipes.API“:
In the next step, select the framework – .NET 6.0 (Long-term support), and check Use controllers:
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:
In the launchSettings.json
delete the launchBrowser
and launchUrl
properties. The JSON should look like this:
{
"$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…
Select MVC Controller – Empty:
Click Add, enter the name RecipeController
and click Add again. Visual Studio will generate an empty Controller with just one method:
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 newClass Library
inside the same solution and name itRecipes.Services
. Right-click on the solution and select Add > New Project..., then select Class Library with C#:
Click “Next” and set the Project name to Recipes.Services:
Hit Next again, set the framework to .NET 6.0 Long-term support, and click Create:
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 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:
Our recipe model will have 4 properties – Name, Servings, Ingredients and PreparationSteps:
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:
using Recipes.Models.Recipe;
namespace Recipes.Services.Recipes
{
public interface IRecipeService
{
IEnumerable<Recipe> GetAllRecipes();
}
}
And, the service method implementation:
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();
:
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:
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:
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:
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:
Add bootstrap
Open the Recipes.AngularClient
with Visual Studio Code and install bootstrap through npm:
npm install bootstrap
Open Index.html
and add the bootstrap style in the head:
<!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
:
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:
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:
Our ApiService
will have 2 methods get and post:
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:
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:
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:
providers: [ApiService, HttpClient, ItemService]
Add the Recipe model
We will create a new file called recipe.model.ts
inside app > models > recipe
:
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:
<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
:
And also, don’t forget to register the HomeComponent
inside the declarations array of the AppModule
:
@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
:
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:
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
:
<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:
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 – 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.