Full-stack application development with AngularJS 11 and Asp.Net MVC Core 5.0 — Creating and Consuming WebServices [part3/4]

Gul Raeez Gulshan
9 min readDec 14, 2020

Make sure you have read and completed the code defined in Part 1 and Part 2 of this series before you begin.

In this article, I will create an HTTP web service that the Angular application can use to request data from ASP.NET Core MVC.

Creating the Web Service

Creating the Web Service Controller

Create a ProductValuesController.cs in the ServerApp/Controllers folder and add the code shown below:

using Microsoft.AspNetCore.Mvc;
using ServerApp.Models;
namespace ServerApp.Controllers
{
[Route("api/products")]
[ApiController]
public class ProductValuesController : Controller
{
private DataContext context;
public ProductValuesController(DataContext ctx)
{
context = ctx;
}
[HttpGet("{id}")]
public Product GetProduct(long id)
{
return context.Products.Find(id);
}
}
}

Testing the Web Service

Go to FullStackApp/ServerApp folder in Windows PowerShell and run the following command:

dotnet run watch

Once the runtime has started, open a web browser and request the URL shown below:

https://localhost:5001/api/products/1

The HTTP request from the browser is received by ASP.NET Core, which passes it on to the MVC framework. The Product object is serialized into JSON, which is the standard data format used for web services.

Using Swagger to Explore the Web Service

We will use Swagger which is also called OpenAPI to test the web service. To install the package, open a new command prompt, navigate to the FullStackApp/ServerApp folder, and run the command.

dotnet add package Swashbuckle.AspNetCore

Enabling and configuring Swashbuckle

Add the following statement at the start.cs file.

services.AddSwaggerGen(options => {
options.SwaggerDoc("v1",
new OpenApiInfo { Title = "FullStackApp API", Version = "v1" });
});
Add outlined statements to IConfiguration Method of startup.cs class
app.UseSwagger();
app.UseSwaggerUI(options => {
options.SwaggerEndpoint("/swagger/v1/swagger.json","FullStackApp API");
});
Add outlined statements to Configure Method of startup.cs class

Open a browser window, and request the URL below.

https://localhost:5001/swagger/v1/swagger.json

The response is a JSON representation of the data types and request types that the web service supports.

Open a new browser window and navigate to the URL below. You will see the interface presented by the Swagger which shows a more easily understood representation of the web service and allows each type of request to be tested.

https://localhost:5001/swagger

Click the GET button, click the Try It Out button, enter 1 in the id text field, and click the Execute button. A GET request will be sent, and the results will be displayed in the browser window, as shown below:

Updating the Controller, View, and Layout of ServeApp

Replacing the Contents of the _Layout.cshtml File in the ServerApp/Views/Shared Folder

<!DOCTYPE html><html lang="en">
<head>
<base href="/">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Full-Stack App</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
<h2 class="bg-dark text-white p-2">Full-Stack App</h2>
@RenderBody()
@RenderSection("Scripts", required: false)
</body>
</html>

To provide the content and the script elements required by the application, replace the contents of the Index.cshtml file in the ServerApp/Views/Home folder with the elements as shown below:

@section scripts {<script src="runtime.js"></script>
<script src="polyfills.js"></script>
<script src="styles.js"></script>
<script src="vendor.js"></script>
<script src="main.js"></script>
}<app-root></app-root>

The scripts section includes script elements for each of the bundle files. The app-root element is the target into which the Angular application’s content will be inserted when the application runs.

Updating the content of app.component.html of AngularApp

Replace the Contents of the app.component.html File in the ClientApp/src/app Folder from the statements given below:

<h2>SportStore</h2>
<span>Angular Content Will Go Here</span>

Testing the connection between ServerApp and ClientApp

Open Windows PowerShell and start the ClientApp by executing the command below:

npm start

Open another Windows PowerShell and start the ServerApp by executing the command below:

dotnet watch run

The final output of the browser will be as shown below:

Consuming the Web Service Data in the Repository

Adding the required module

We need to import HttpClientModule in the model. module.ts file in the ClientApp/src/app/models folder to make HTTP requests in Angular application. This module contains the Angular functionality for making HTTP requests and processing the responses

Adding the repository class

Next, add the Angular Repository class so that it gets its data from the web service.

import { Product } from "./product.model";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
@Injectable()
export class Repository {
product: Product;
constructor(private http: HttpClient) {
this.getProduct(1);
}
getProduct(id: number) {
this.http.get<Product>("/api/products/" + id)
.subscribe(p => this.product = p);
}
}

Registering the Repository Service to app.module.ts

Update the Contents of the app.component.ts

Working with the Repository in the app.component.ts File in the ClientApp/src/app Folder

import { Component } from '@angular/core';
import { Repository } from "./models/repository";
import { Product } from "./models/product.model";
@Component({selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})export class AppComponent {constructor(private repo: Repository) { }get product(): Product {return this.repo.product;
}
}

Update the Contents of the app.component.html

<div class="p-2"><table class="table table-sm">
<tr><th>Name</th><td>{{product?.name}}</td></tr>
<tr><th>Category</th><td>{{product?.category}}</td></tr>
<tr><th>Description</th><td>{{product?.description}}</td></tr>
<tr><th>Price</th><td>{{product?.price}}</td></tr>
</table>
</div>

Displaying the data Save the changes to the TypeScript files, and the browser will reload to display the table that has been generated dynamically from the data.

Eager Loading of related data in ServerApp

To avoid loading data that may not be needed, Entity Framework Core does not load related data unless specifically instructed to do so. To include the Supplier and Rating objects that are associated with a Product object, change the query performed by the web service controller, as shown below:

[HttpGet("{id}")]public Product GetProduct(long id)
{
Product result = context.Products
.Include(p => p.Supplier).ThenInclude(s => s.Products)
.Include(p => p.Ratings)
.FirstOrDefault(p => p.ProductId == id);
if (result != null)
{
if (result.Supplier != null)
{
result.Supplier.Products = result.Supplier.Products.Select(p =>
new Product
{
ProductId = p.ProductId,
Name = p.Name,
Category = p.Category,
Description = p.Description,
Price = p.Price,
});
}
if (result.Ratings != null)
{
foreach (Rating r in result.Ratings)
{
r.Product = null;
}
}
}
return result;
}

Configuring the JSON Serializer in the Startup.cs File in the ServerApp Folder

Displaying Related Data in the app.component.html File in the ClientApp/src/app Folder

<div class="p-2">
<table class="table table-sm">
<tr><th colspan="2" class="bg-info">Product</th></tr>
<tr><th>Name</th><td>{{product?.name || 'Loading Data...'}}</td></tr>
<tr><th>Category</th><td>{{product?.category || 'Loading Data...'}}</td></tr>
<tr>
<th>Description</th>
<td>{{product?.description || 'Loading Data...'}}</td>
</tr>
<tr><th>Price</th><td>{{product?.price || 'Loading Data...'}}</td></tr>
<tr><th colspan="2" class="bg-info">Supplier</th></tr>
<tr><th>Name</th><td>{{product?.supplier?.name}}</td></tr>
<tr><th>City</th><td>{{product?.supplier?.city}}</td></tr>
<tr><th>State</th><td>{{product?.supplier?.state}}</td></tr>
<tr><th>Products</th><td>{{product?.supplier?.products?.length}
</td></tr>
</table>
</div>

When you save the change to the template and navigate to https://localhost:5001, the browser will display the content shown below:

Implementing the GET Method for Multiple Objects

Adding an Action in the ProductValuesController.cs File in the ServerApp/Controllers Folder

[HttpGet]
public IEnumerable<Product> GetProducts(bool related = false)
{
IQueryable<Product> query = context.Products;
if (related)
{
query = query.Include(p => p.Supplier).Include(p => p.Ratings);
List<Product> data = query.ToList();
data.ForEach(p => {
if (p.Supplier != null)
{
p.Supplier.Products = null;
}
if (p.Ratings != null)
{
p.Ratings.ForEach(r => r.Product = null);
}
});
return data;
}
else
{
return query;
}
}

The GetProducts method is decorated with the HttpGet attribute that can be used to handle GET requests. The action method defines a related parameter that is used to indicate whether related data should be included in the response; the parameter defaults to false.

To test the new action, restart the ASP.NET Core MVC application and use a browser to request https://localhost:5001/api/products. This URL requests the products without related data and will produce a result with data like below:

To include related data in the request, use a browser to request https://localhost:5001/api/products?related=true, which will produce data like below:

Querying Multiple Objects in the Angular Application

Updating repository.ts File in the ClientApp/src/app/model Folder.

import { Product } from "./product.model";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
const productsUrl = "/api/products";@Injectable()
export class Repository {
product: Product;
products: Product[];
constructor(private http: HttpClient) {
this.getProducts(true);
}
getProduct(id: number) {
this.http.get<Product>(`${productsUrl}/${id}`)
.subscribe(p => this.product = p);
}
getProducts(related = false) {
this.http.get<Product[]>(`${productsUrl}?related=${related}`)
.subscribe(prods => this.products = prods);
}
}

Defining a Property in the app.component.ts File in the ClientApp/src/app Folder

get products(): Product[] {
return this.repo.products;
}

Updating the app.component.html File in the ClientApp/src/app Folder

<div class="p-2">
<table class="table table-sm table-striped">
<tbody>
<tr>
<th>Name</th>
<th>Category</th>
<th>Price</th>
<th>Supplier</th>
<th>Ratings</th>
</tr>
<tr *ngFor="let product of products">
<td>{{product.name}}</td>
<td>{{product.category}}</td>
<td>{{product.price}}</td>
<td>{{product.supplier?.name || 'None'}}</td>
<td>{{product.ratings?.length || 0}}</td>
</tr>
</tbody>
</table>
</div>

Navigating to https://localhost:5001 will result as shown below:

Applying Filtering in the Web Service

Updating the GetProducts Action Method of ProductValuesController.cs File in the ServerApp/Controllers Folder

public IEnumerable<Product> GetProducts(string category, string search, bool related = false)
{
IQueryable<Product> query = context.Products;
if (!string.IsNullOrWhiteSpace(category))
{
string catLower = category.ToLower();
query = query.Where(p => p.Category.ToLower().Contains(catLower));
}
if (!string.IsNullOrWhiteSpace(search))
{
string searchLower = search.ToLower();
query = query.Where(p => p.Name.ToLower().Contains(searchLower)
|| p.Description.ToLower().Contains(searchLower));
}
if (related)
{
query = query.Include(p => p.Supplier).Include(p => p.Ratings);
List<Product> data = query.ToList();
data.ForEach(p => {
if (p.Supplier != null)
{
p.Supplier.Products = null;
}
if (p.Ratings != null)
{
p.Ratings.ForEach(r => r.Product = null);
}
});
return data;
}
else
{
return query;
}
}

To test the ability to filter data, restart the ASP.NET Core MVC application, and use a browser to request https://localhost:5001/api/products?category=education. This URL requests the Product objects in the Education category and will produce the following result as below:

Applying Filtering in the Angular Application

Add a TypeScript file called configClasses.repository.ts to the models' folder and use it to define the class shown below:

export class Filter {
category?: string;
search?: string;
related: boolean = false;
reset() {
this.category = this.search = null;
this.related = false;
}
}

This class will be used to specify the filtering that will be applied to product data.

Updating the repository.ts File in the ClientApp/src/app/models Folder.

When you save the changes to the TypeScript file and navigate to https://localhost:5001, the browser will show the filtered data as shown below:

Summary

In this article, I created HTTP web service that follows the basic principles of REST. I created a new MVC controller for handling data requests. In the next article, I will continue to work on the web service providing the features that are required to create, modify, and delete data objects.

The full code of this article can be downloaded from the Github repository.

https://github.com/gulraizgulshan2k18/angular-mvc-fullstack

--

--