In this module we are going to pick up where we left off with our paging and sorting back in module 18, MatTable: Client-Side Paging and Sorting. Right now we are using client-side paging and sorting with no customization. We just fetch all the Vehicle records from the database in ngOnInit()
, assign the API JSON result to the MatTableDataSource, and point the MatTableDataSource paginator and sort properties to the Child MatPaginator and MatSort child components in ngAfterInit()
. Then we just let the MatTableDataSource paging and sorting do it’s thing. We don’t have to do any tracking of page indexes, page sizes, sort columns, or sort directions. We just wire everything up.
There is nothing wrong with this approach, especially if you only have a dozen or so records like we do. But if we have happen to have hundreds of thousands or even millions of records then this would definitely be a problem because all of the records are currently being fetched at once and stored on the users computer in client-side memory. The user will definitely have to wait a long time for the initial load and that’s if the browser doesn’t timeout, or just exceed memory and crash.
In the next several sections we are going to improve upon this infrastructure by modifying our Vehicles API and Angular component’s TypeScript and HTML to implement custom server-side paging.
Add paging parameters to Vehicles API
The first thing we need to do to implement server-side paging is add paging parameters to the HTTP GET
method in the Vehicles controller. Open the VehiclesController.cs file and make the modifications shown below.
FredsCarsAPI/Controllers/VehiclesController.cs
namespace FredsCarsAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class VehiclesController : ControllerBase
{
private ApplicationDbContext _context;
public VehiclesController(ApplicationDbContext context)
{
_context = context;
}
[HttpGet]
public async Task<IEnumerable<VehicleDTO>> GetVehicles(
int pageIndex = 0, int pageSize = 10)
{
var vehicles = await _context.Vehicles
.Include(v => v.VehicleType)
.ToListAsync();
var vehicleDTOs = new List<VehicleDTO>();
foreach (var vehicle in vehicles)
{
vehicleDTOs.Add(ConvertVehicleToDTO(vehicle));
}
return vehicleDTOs
.Skip(pageIndex * pageSize)
.Take(pageSize);
}
/*** Existing Code ***/
In the code above we have just made a couple simple modifications. First, we added two parameters to the HTTP GET GetVehicles()
method; pageIndex and pageSize. And given them the default values of 0 and 10 respectively. We then use these two parameters in the return statement to calculate how many pages to skip and records to take at once using the LINQ Skip()
and Take()
methods.
Now if we run a swagger test for the Vehicles HTTP GET API service similar to the one we ran back in module 18 like the one shown below and pass the value 2 for pageIndex and 5 for pageSize:

We get the last two out of twelve records as a response.

And success! I’d say we are well on our way to gaining server-side control of our paging and implementing our custom paging version and we were able to do so very quickly.
However we are going to need more information contained in our JSON result now in addition to just our Vehicle records for the client-side to do it’s part in the custom paging implementation.
Add paging information to the JSON result
In addition to our Vehicle data we need to send back the following information in our JSON response with each page request:
pageIndex
pageSize
totalCount
– of recordstotalPages
Make the following modifications to VehiclesController.cs.
FredsCarsAPI/Controllers/VehiclesController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace FredsCarsAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class VehiclesController : ControllerBase
{
private ApplicationDbContext _context;
public VehiclesController(ApplicationDbContext context)
{
_context = context;
}
[HttpGet]
public async Task<Object> GetVehicles(int pageIndex = 0, int pageSize = 10)
{
// get Vehicle count
var count = await _context.Vehicles.CountAsync();
// get total number of pages
var totalPages = (int)Math.Ceiling(count / (double)pageSize);
// get Vehicles page
var vehicles = await _context.Vehicles
.Include(v => v.VehicleType)
.Skip(pageIndex * pageSize)
.Take(pageSize)
.ToListAsync();
// convert Vehciles to DTOs
var vehicleDTOs = new List<VehicleDTO>();
foreach (var vehicle in vehicles)
{
vehicleDTOs.Add(ConvertVehicleToDTO(vehicle));
}
// return new JSON result with data and additional paging info
return new
{
data = vehicleDTOs,
pageIndex = pageIndex,
pageSize = pageSize,
// record count
totalCount = count,
totalPages = totalPages,
};
}
/*** Existing Code ***/
Now if we run the same swagger test with a pageIndex parameter of 2 and pageSize of 5 we will get the following JSON result:
{
"data": [
{
"id": 11,
"status": "New",
"year": "2022",
"make": "Jeep",
"model": "Grand Cherokee",
"color": "Pearlcoat",
"price": 53575,
"vin": "1C4RJKBG5M8201121",
"vehicleType": "Jeep"
},
{
"id": 12,
"status": "New",
"year": "2021",
"make": "Jeep",
"model": "Wrangler Sport S",
"color": "Green",
"price": 40940,
"vin": "1C4GJXAN0MW856433",
"vehicleType": "Jeep"
}
],
"pageIndex": 2,
"pageSize": 5,
"totalCount": 12,
"totalPages": 3
}
In the above JSON result we have returned a JavaScript array with five properties:
data
: contains our Vehicle[DTO] datapageIndex
: the page being returnedpageSize
: the size of the page for the requesttotalCount
: the total count of vehicle records in the databasetotalPages
: the total number of pages given the total database record count and page size.
Now let’s examine the new code that generates the more complex JSON result.
Within the HTTP GET GetVehicles()
method, the first thing we do is change the type of object we return from IEnumerable<VehicleDTO>
to Object
. This is because we are going to build up and return an anonymous object as a JSON result.
The next thing we do is store a couple of pieces of information in some local variables. First we asynchronously query the count of the Vehicles in our DbContext to get the total count of records and store it in a local variable called count. We are able to do this asynchronously because we have marked the method with the async keyword and because Vehicles is a DbSet in our DbContext which inherits from IQueryable. And IQueryable supports asynchronous methods such as CountAsync()
where as a List does not. So we wait until further down to use the ToListAsync()
method.
Next we calculate the total number of pages taking into account our count variable and the pageSize and store the result in a local variable called totalPages.
Then we fill our vehicles local variable by again querying the Vehicles DbSet and using the Skip()
and Take()
LINQ methods just as before to narrow our request down to one page. At the end of the query we are finally able to convert the IQueryable DbSet to a list with ToListAsync()
.
Next, the conversion process of Vehicles to VehicleDTOs remains unchanged.
And finally the good part. At the end of the method we return an anonymous object using the new
keyword with the five properties we set out above to implement. The properties set at this point for the returned JSON object should be pretty self descriptive.
Modify the Vehicles component
Now that we have our Vehicles API GET request where we want it, we need to update the Vehicles component on the Angular side to consume the new modified JSON request and handle the custom paging from the client-side. (Remember, this is still server-side paging we are working on.)
Modify the Vehicles TypeScript
Open the vehicles.component.ts file and make the modifications below.
FredsCars/src/app/vehicles/vehicles.component.ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Vehicle } from './vehicle';
import { environment } from '../../environments/environment.development';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { Category, VehicleType } from './vehicleType';
@Component({
selector: 'app-vehicles',
templateUrl: './vehicles.component.html',
styleUrls: ['./vehicles.component.scss']
})
export class VehiclesComponent implements OnInit {
// debug JSON var for HTML
public json = JSON;
public vehicles!: MatTableDataSource<Vehicle>
columnsToDisplay: string[] = ['id', 'vehicleType', 'status', 'year', 'make', 'model', 'color', 'price'];
@ViewChild(MatPaginator) paginator!: MatPaginator;
// @ViewChild(MatSort) sort!: MatSort;
// Category Search Checkbox client-model
categoryAll: Category = {
id: 0,
name: 'All',
selected: false,
categories: []
};
allCategoriesSelected: boolean = false;
constructor(private http: HttpClient) {
// Instantiate the MatTableDataSource.
this.vehicles = new MatTableDataSource<Vehicle>();
}
ngOnInit() {
// get vehicles
var pageEvent = new PageEvent();
pageEvent.pageIndex = 0;
pageEvent.pageSize = 5;
this.getVehicleData(pageEvent);
// get vehcileTypes and transform into search categories
// for sidenav
this.http.get<VehicleType[]>(environment.baseUrl + 'api/vehicleTypes').subscribe(result => {
result.forEach(vt => {
this.categoryAll.categories?.push(
{
id: vt.id,
name: vt.name,
selected: false
}
);
})
}, error => console.error(error));
}
ngAfterViewInit() {
// this.vehicles.paginator = this.paginator;
// this.vehicles.sort = this.sort;
}
getVehicleData(event: PageEvent) {
var url = environment.baseUrl + 'api/vehicles';
var params = new HttpParams()
.set("pageIndex", event.pageIndex.toString())
.set("pageSize", event.pageSize.toString());
this.http.get<any>(url, { params })
.subscribe(result => {
this.paginator.length = result.totalCount;
this.paginator.pageIndex = result.pageIndex;
this.paginator.pageSize = result.pageSize;
// this.vehicles = new MatTableDataSource<Vehicle>(result.data);
this.vehicles.data = result.data
}, error => console.error(error));
}
/*** Category Search Checkbox methods ***/
public someSearchCategoriesSelected(): boolean {
return this.categoryAll.categories!.filter(cat =>
cat.selected).length > 0 && !this.allCategoriesSelected;
}
setAllCategoriesSelected(checked: boolean) {
this.allCategoriesSelected = checked;
this.categoryAll.selected = checked;
this.categoryAll.categories!.forEach(cat =>
(cat.selected = checked));
}
updateAllCategoriesSelected() {
var allSelected: boolean =
this.categoryAll.categories!.every(cat =>
cat.selected);
this.allCategoriesSelected = allSelected;
this.categoryAll.selected = allSelected;
}
/*** End Category Search Checkbox methods ***/
}
Let’s inspect the modifications to our Vehicles component TypeScript above.
In the top import section we added in HttpParams
from the @angular/common/http library
and PageEvent
from @angular/material/paginator
. We need HttpParams so that our Angular call to the Vehicles API GET request can add in the new pageIndex and pageSize parameters. And we need PageEvent so we can manually set the MatPaginator properties when the component is first initialized and each time the user uses the paging controls; such as clicking the first page, last page, next and previous pages or changes the number of records shown per page.
We comment out the @ViewChild sort property for now so we can keep our mind on paging and isolate our development efforts on that for the moment.
ngOnInit()
is where it starts to get interesting. First we declare a variable called pageEvent
and instatiate it as a PageEvent
object for MatPaginator. We set it’s pageIndex
property to 0 for the first page and it’s pageSize
property to 5 so five records will be grabbed as the component first initializes. We then break out the http.get()
call to the Vehicles API HTTP GET request into a new method called getVehicleData
and pass it the PageEvent
object we’ve just built up.
We also comment out setting the MatTableDataSource’s paginator and sort properties in ngAfterInit()
we previously had for default client-side paging and sorting because we are going to handle paging and sorting manually now in getVehicleData()
every time the user interacts with the paging controls.
Next comes the new getVehicleData() method we have already passed a PageEvent object to from ngOnInit(). We will also be wiring up a page
event on MatPaginator in the HTML in the next section so every time the user interacts with the paging controls an event will be sent to getVehicleData()
and the event received will also be a PageEvent. First, in this method, we build the url variable as usual. Next we do something new and create a variable called params of type HttpParams and set two parameters for our API call; pageIndex and pageSize. These are set to the properties of the same name of the PageEvent parameter received either from ngOnInit() or the MatPaginator component itself. We then execute an http.get() request also as usual with a few new twists. We have changed the return type of the request from Vehicle[]
to any
. This is because we are no longer just receiving an array of vehicles from the get request but a more complicated JSON object with the data property for vehicles and our other properties such as pageIndex, pageSize, totalCount, and totalPages. We subscribe to the http.get()
observable and use the result to set the MatPaginator length, pageIndex, and pageSize properties. Also, within the subscribe we now set the vehicles MatTableDataSource.data property to the data property of the JSON result which is an array of vehicles.
Modify the Vehicles HTML Template
And finally let’s update the Vehicles component HTML. Open the vehicles.component.html file and make the modifications below.
FredsCars/src/app/vehicles/vehicles.component.html
<mat-sidenav-container>
<mat-sidenav mode="side" #sidenav>
<div style="margin-right: 10px;">
<p><b>Search Panel</b></p>
<hr />
<p><b>Categories</b></p>
<mat-checkbox color="primary"
[checked]="allCategoriesSelected"
[indeterminate]="someSearchCategoriesSelected()"
(change)="setAllCategoriesSelected($event.checked)">
{{ categoryAll.name }}
</mat-checkbox>
<ul>
<li *ngFor="let category of categoryAll.categories">
<mat-checkbox color="accent"
[(ngModel)]="category.selected"
(ngModelChange)="updateAllCategoriesSelected()">
{{category.name}}
</mat-checkbox>
</li>
</ul>
<!-- debug -->
<!--
<b>categoryAll:</b><br />
{{ categoryAll.selected }}<br />
<b>Categories:</b>
<ul>
<li *ngFor="let category of categoryAll.categories">
{{category.name}}: {{ category.selected }}
</li>
</ul>
-->
<!-- debug -->
</div>
</mat-sidenav>
<mat-sidenav-content>
<p *ngIf="!(vehicles.data.length > 0)">
<mat-spinner style="margin: 0 auto;"></mat-spinner>
</p>
<div *ngIf="vehicles.data.length > 0">
<button mat-icon-button
color="primary"
(click)="sidenav.toggle()">
<mat-icon>
search
</mat-icon>
</button>
</div>
<table mat-table [dataSource]="vehicles" matSort
[ngClass]="{'hide-results' : vehicles.data.length <= 0}">
<ng-container matColumnDef="vehicleType">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Category</th>
<td mat-cell *matCellDef="let item">
<b>{{ item.vehicleType }}</b>
</td>
</ng-container>
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>
<td mat-cell *matCellDef="let item">
{{ item.id }}
</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Status</th>
<td mat-cell *matCellDef="let item">
{{ item.status }}
</td>
</ng-container>
<ng-container matColumnDef="year">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Year</th>
<td mat-cell *matCellDef="let item">
{{ item.year }}
</td>
</ng-container>
<ng-container matColumnDef="make">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Make</th>
<td mat-cell *matCellDef="let item">
{{ item.make }}
</td>
</ng-container>
<ng-container matColumnDef="model">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Model</th>
<td mat-cell *matCellDef="let item">
{{ item.model }}
</td>
</ng-container>
<ng-container matColumnDef="color">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Color</th>
<td mat-cell *matCellDef="let item">
{{ item.color }}
</td>
</ng-container>
<ng-container matColumnDef="price">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Price</th>
<td mat-cell *matCellDef="let item">
<b>{{ item.price | currency:"USD" }}</b>
</td>
</ng-container>
<!-- No VIN on vehicles page. We will show this on the details page -->
<!-- Header Template -->
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<!-- Row Template -->
<tr mat-row *matRowDef="let row; columns: columnsToDisplay"></tr>
</table>
<mat-paginator (page)="getVehicleData($event)"
[pageSize]="5"
[pageSizeOptions]="[3, 5, 10]"
showFirstLastButtons
[ngClass]="{'hide-results' : vehicles.data.length <= 0}">
</mat-paginator>
</mat-sidenav-content>
</mat-sidenav-container>
The main change in the above HTML for the Vehicles component is that we added in a page
event for the mat-paginator
element and bound it to our new getVehiclesData()
method in the TypeScript. This is a very quick and easy change now that our Vehicles Component TypeScript now has the infrastructure to support custom paging.
In addition, I’ve also added in a ngIf
structural directive on the div
element containing the search mat-icon
for the mat-icon-button
which allows the user to open the SideNav.
Run the project: Test the custom server-side paging.
Now when you run the project and navigate to the Vehicles component, the results should be the same as it was with default client-side paging as shown below. You can test the paging out for yourself by interacting with the paging controls. And the application should behave the same as it has since module 18, MatTable: Client-Side Paging and Sorting.

Well, we have certainly improved the performance so far by switching to custom server-side paging. Now if we ever have to deal with millions of vehicles, just in case good ol Fred ever becomes a National Franchise and we are keeping track of automobiles on auto-lots all across the USA, we only fetch, 3, 5, or 10 records at a time, or whatever we set the paging options to in the drop down which would more likely be 20, 50, or 100 in a real life scenario. The trade-off is whenever the user clicks to go to another page it will be a little slower than downloading all the records in one go on the initial component load. But the hit is negligible and it is a good balance.
Wow! I think we have really accomplished a lot in this module so far with pretty minimal changes. But if we step back and take another look at all of our changes in this module and our code as it stands now, is it really the best it can be? Let’s think about it.
Remember earlier back to our Vehicles API changes in the HTTP GET method? Most of our code is paging logic. In fact about 8 out of 20 lines of code are for paging rather then just querying vehicle data and returning a response. Four lines are dedicated to converting Vehicles to VehicleDTO objects. Only the leftover 8 lines are actually for querying actual vehicle data and returning a response which are the main responsibilities of a controller. This code looks pretty WET (Write Everything Twice, or We Enjoy Typing) rather then DRY (Do Not Repeat Yourself).
What if we need other components to implement paging? Are we going to copy this code in every instance? What if we improve our paging logic? Will we have to update multiple components?
Let’s see if we can improve our paging code and make it a bit more reusable.
Using Generics to implement paging reuse
In this section we are going to break out the paging logic from our Vehicles API HTTP GET method into a generic ApiResult class.
Create a new class file called ApiResult.cs in the FredsCarsApi/Data folder and fill it with the contents below.
FredsCarsAPI/Data/ApiResult.cs
using Microsoft.EntityFrameworkCore;
namespace FredsCarsAPI.Data
{
public class ApiResult<T>
{
private ApiResult(
List<T> data,
int count,
int pageIndex,
int pageSize)
{
Data = data;
PageIndex = pageIndex;
PageSize = pageSize;
TotalCount = count;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
}
// factory method
public static async Task<ApiResult<T>> CreatAsync(
IQueryable<T> source,
int pageIndex,
int pageSize)
{
var count = await source.CountAsync();
source = source
.Skip(pageIndex * pageSize)
.Take(pageSize);
var data = await source.ToListAsync();
return new ApiResult<T>(
data,
count,
pageIndex,
pageSize
);
}
public List<T> Data { get; private set; }
public int PageIndex { get; private set; }
public int PageSize { get; private set; }
// total record count
public int TotalCount { get; private set; }
public int TotalPages { get; private set; }
}
}
The code above is a generic class called ApiResult
and it’s class signature is:
public class ApiResult<T>
We pronounce this as “ApiResult of type T”. We talk more about generics in part 2 where we learned all about ASP.Net Core. But as a review this signature means our new ApiResult class will be used to offload paging work for a list of any type of object. We could be paging a list of Vehicles, Users for an administration tool, or any other type of class we need paging for.
The constructor for the class takes in four parameters:
- data of type List<T>:
a List of T where T can be any Type we instantiate ApiResult with. - count: of type int
- pageIndex: of type int
- pageSize: of type int
At the bottom of the code we have five properties properties declared with get
and private set
accessors:
- Data: of type List<T>:
a List of T where T can be any Type we instantiate ApiResult with. - PageIndex: of type int
- PageSize: of type int
- TotalCount: of type int
- TotalPages: of type int
The body of the constructor assigns the incoming parameters (data, pageIndex, pageSize, and count) to the class properties (Data, PageIndex, PageSize, TotalCount) and calculates the value of the TotalPages property using the incoming count and pageSize parameters.
What we are creating here with ApiResult can be thought of as a paging service. We are eventually going to need to instantiate this service from the Vehicles controller HTTP Get method. And since we are dealing with database data we are going to want to do this asynconously. The problem is that a constructor method cannot be asynchronous. So we created an asyncronous factory method called CreatAsync()
.
The method signature for CreatAsync()
looks like this:
public static async Task<ApiResult<T>> CreatAsync(
IQueryable<T> source,
int pageIndex,
int pageSize)
The above method signature says I am an asynchronous method (using the async keyword) and I return a an ApiResult of Type T.
I take in an IQueryable of Type T as the datasource to be queried, a pageIndex, and a pageSize both of which are integers.
Remember the incoming IQueryable<T>
parameter here for our factory method. This is very important. When we instantiate the ApiResult<T> class from the Vehicles controller we do not want to send in a List<T>. That would mean we have already hit the database and fetched potentially millions of records from the database. We just want to set up a IQueryable<T> meaning the query has not been executed yet so our ApiResult<T> paging service can further modify the query with paging logic and execute it.
Finally, in the body of the CreatAsync()
factory method we impliment the paging logic and return a new ApiResult<T> object which in this case is going to be an ApiResult<VehicleDTOs>. Remember we want VehicleDTOs (Data Transfer Objects) in our JSON’s data rather then simple Vehicles objects.
We do this by:
- getting the count of total records using the
IQuerybale<T>.CountAsync()
method and assigning it to a local variable named count. - modifying the IQueryable<T> source variable with the LINQ
Skip()
andTake()
methods to grab only one page. - executing the source query with the LINQ
ToListAsync()
method and assigning the results to a local variable called data. - and returning a new ApiResult of VehicleDTO by calling its constructor with the new keyword and passing to it the local variables we just built up (data and count) and the incoming parameters (pageIndex and pageSize).
Modify the Vehicles API HTTP GET method
Now that we have the ApiResult service in place it’s time to call it and use it from the Vehicles controller.
Open the VehiclesController.cs file and make the modifications shown below.
FredsCarsAPI/Controllers/VehiclesController.cs
/*** existing code ***/
[HttpGet]
public async Task<ApiResult<VehicleDTO>> GetVehicles(
int pageIndex = 0, int pageSize = 10)
{
// get Vehicles page
var dataQuery = _context.Vehicles.AsNoTracking()
.Include(v => v.VehicleType)
return await ApiResult<VehicleDTO>.CreatAsync(
dataQuery,
pageIndex,
pageSize);
}
/*** existing code ***/
In the code above we have changed the return type of our HTTP GET call from type Object
to type ApiResult<VehicleDTO>
.
We then set up a LINQ query on the Vehicles DbSet from our DbContext and chain the AsNoTracking()
method since we are not doing any updates. This method skips the overhead of tracking Entity changes. And we also use the LINQ Include() method to eagerly load the VehicleType obect property of all retrieved Vehicles as before. We will need this information to convert our Vehicles to VehcileDTOs.
Finally, we return a new ApiResult<VehicleDTO>
by calling the APIResult<T>.CreateAsync()
factory method and passing to it the unexecuted dataQuery we just built up and the incoming pageIndex and pageSize parameters.
At this point we should be good to go right? Well, not exactly. Take a closer look at this statement:
// get Vehicles page
var dataQuery = _context.Vehicles.AsNoTracking()
.Include(v => v.VehicleType)
We are passing an IQueryable for Vehicles to the ApiResult factory method. But we want ApiResult to return an array of VehicleDTOs in the data property of the JSON result. Not simple Vehicle objects. And ApiResult should not be responsible for the conversion of Vehicles to VehicleDTOs. After all. It’s generic! Right? Its a generic service to offload paging work, not to convert types of Lists, IEnumerables, or IQueryables.
So, how can we convert the Vehicles to VehicleDTOs before passing the query to ApiResult without first executing the query? There is a way.
If the problem we are trying to solve is still a little fuzzy, look back at the original Vehicles to VehilceDTOs logic we had in the Vehicle Controller.
foreach (var vehicle in vehicles)
{
vehicleDTOs.Add(ConvertVehicleToDTO(vehicle));
}
There are two ways to execute a LINQ query, or the IQueryable we are working with. One is to use the ToList()
or ToListAsync()
method. The other is to iterate through the IQueryable
with a foreach
loop.
This is what we are trying to avoid in the controller before sending the IQueryable to ApiResult; executing the query.
Use an extension method to defer execution
We are going to use an extension method to solve this problem. Create a new folder called ExtensionMethods on the root of the FredsCarsAPI project in Solution Explorer and in the new folder create a class file named ExtensionMethods.cs and fill it with the contents below.
FredsCarsAPI/ExtensionMethods/ExtensionMethods.cs
using FredsCarsAPI.Models;
using FredsCarsAPI.Models.DTOs;
using Microsoft.EntityFrameworkCore.Query;
namespace FredsCarsAPI
{
public static class ExtensionMethods
{
public static IQueryable<VehicleDTO>
ConvertVehiclesToDTOs(this IQueryable<Vehicle> vehiclesQuery)
{
return vehiclesQuery.Select(v => new VehicleDTO
{
Id = v.Id,
Status = v.Status,
Year = v.Year,
Make = v.Make,
Model = v.Model,
Color = v.Color,
Price = v.Price,
VIN = v.VIN,
VehicleType = v.VehicleType.Name
});
}
}
}
The code above is an extension method used to extend an IQueryable<Vehicle>
type class by adding to it a method called ConvertVehiclesToDTOs()
. We are really using this method to convert a LINQ query from a LINQ to Entities query (as in Entity Framework Entities) to a LINQ to Objects query.
To create an extension method first it has to be in a static class. And we have marked the class it resides in, ExtensionMethods, as static.
Secondly, the extension method itself must be static as shown in the method signature below.
public static IQueryable<VehicleDTO>
ConvertVehiclesToDTOs(this IQueryable<Vehicle> vehiclesQuery)
The this
keyword marks the type of class we are extending, in this case IQueryable<Vehicle>
.
The body of the extension method modifies and returns the query by using the LINQ Select()
method to project a new VehicleDTO for every Vehicle. Select is a deferred LINQ method so again the query is not executed and we return the the modified query which matches the return type of the method in the signature above, IQueryable<VehicleDTO>.
Now we can use this method back in the Vehicles Controller HTTP GET call and chain it to the end of the data query build up.
Open up VehiclesController.cs once again and make the modification below.
FredsCarsAPI/Controllers/VehiclesController.cs
/*** existing code ***/
[HttpGet]
public async Task<ApiResult<VehicleDTO>> GetVehicles(
int pageIndex = 0, int pageSize = 10)
{
// get Vehicles page
var dataQuery = _context.Vehicles.AsNoTracking()
.Include(v => v.VehicleType)
.ConvertVehiclesToDTOs();
return await ApiResult<VehicleDTO>.CreatAsync(
dataQuery,
pageIndex,
pageSize);
}
/*** existing code ***/
In the above code we have added our new extension method call to the data query build up using chaining with LINQ fluent API. We are now converting our query of Vehicles to a query of VehicleDTOs before ever passing it to ApiResult so now all ApiResult has to worry about is modifying the query to take one page of records.
Testing the deferred execution
If you want to test this notion out, put a break point on the return statement of the Vehicles Controller HTTP GET method’s return statement, run the project in debug mode, and inspect the dataQuery variable with a quick watch as shown in the screen shot below.

It’s a little hard to make out in the screen shot above. But, the dataQuery variable once converted by our extension method is indeed a type of System.Linq.IQueryable<FredsCarsAPI.Models.DTOs.VehicleDTO>
.
We’ve also reduced the number of lines in the HTTP GET method from 20 lines down to two lines. One of the goals of .Net has always been to reduce the number of lines of code as much as possible. And the code is much more readable now. We are just building up a query and passing it to a service that will return a JSON result to the controller and the controller can return that JSON result as the controller method result.
What’s Next
In this module we encapsulated the MatPaginator paging duties in the ApiResult class. This helps keep the controller more readable and testable. When we create unit tests for the controller, it will be easier now to determine if any bugs are the fault of the actual controller, or the result of any paging logic errors. Also the paging logic itself will be more testable now that it is isolated out.
I’ll admit this was a pretty involved module. But it shed a lot of insight into some important software engineering principles such as DRY and software methodology. There is always a lot of refactoring. We took several passes at the Vehicles API Get method to make its code clean, maintainable, and testable.
Before moving on to sorting, in the next module we are going to start writing some unit tests for the Vehicles controller, the ApiResult class and our extension method.