Full-stack application development with AngularJS 11 and Asp.Net MVC Core 5.0 — Creating and Consuming WebServices [part3/4]
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" });
});
app.UseSwagger();
app.UseSwaggerUI(options => {
options.SwaggerEndpoint("/swagger/v1/swagger.json","FullStackApp API");
});
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.