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

Image for post
Image for post

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);
}
}
}
Image for post
Image for post

Testing the Web Service

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

dotnet run watch
Image for post
Image for post

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.

Image for post
Image for post

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
Image for post
Image for post
Image for post
Image for post

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" });
});
Image for post
Image for post
Add outlined statements to IConfiguration Method of startup.cs class
app.UseSwagger();
app.UseSwaggerUI(options => {
options.SwaggerEndpoint("/swagger/v1/swagger.json","FullStackApp API");
});
Image for post
Image for post
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.

Image for post
Image for post

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
Image for post
Image for post

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:

Image for post
Image for post

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>
Image for post
Image for post

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>
Image for post
Image for post

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>
Image for post
Image for post

Testing the connection between ServerApp and ClientApp

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

npm start
Image for post
Image for post

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

dotnet watch run
Image for post
Image for post

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

Image for post
Image for post

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

Image for post
Image for post

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);
}
}
Image for post
Image for post

Registering the Repository Service to app.module.ts

Image for post
Image for post

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;
}
}
Image for post
Image for post

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>
Image for post
Image for post

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.

Image for post
Image for post

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;
}
Image for post
Image for post

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

Image for post
Image for post

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>
Image for post
Image for post

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

Image for post
Image for post

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;
}
}
Image for post
Image for post

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:

Image for post
Image for post

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:

Image for post
Image for post

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);
}
}
Image for post
Image for post

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

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

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>
Image for post
Image for post

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

Image for post
Image for post

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;
}
}
Image for post
Image for post

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:

Image for post
Image for post

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;
}
}
Image for post
Image for post

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.

Image for post
Image for post

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:

Image for post
Image for post

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

Artificial Intelligence Enthusiast, SDN Developer, Virtualization Expert

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store