Skip to content
  • iImagine
  • Register
  • Log In

Web Development School

Learning made easy.

  • Books
    • Beginning Web Development with ASP.Net Core & Client-Side Technologies
      • TOC
      • Part 1
        • Chapter 1: Static HTML – Designing the landing page
      • Part 2
        • Chapter 2: ASP.Net Core – Let’s talk Dynamic
        • Chapter 3: Introduction to ASP.Net Core MVC
          [ASP.Net Core v9]
      • Part 4
        • Chapter 7: Using Server Side & Client Side technologies together
          [ASP.Net Core v7 & Angular 15]
  • Environment Setup
    • Installing Angular
    • Installing Visual Studio 2022
    • Installing SQL Server 2022 Express
    • Installing Postman
    • Installing Git for Windows
  • Blog
  • iImagine WebSolutions
  • Events
  • Learning Videos
  • Toggle search form

Filter (Quick Search)

Well we have spent the last 7 modules since module 23, MatPaginator & PageEvent: Custom Server-Side Paging, implementing server-side paging and sorting. We have done a lot of work to put all of the paging and sorting logic into the ApiResult class, make all of this logic DRY, loosely coupled, and designed for easy unit testing.

To make all of this happen, we have added paging and sorting HTTP parameters to the Rest API call on the Angular side, and paging and sorting incoming parameters in the Vehicles controller HTTP GET method and ApiResult factory method. I do want to eventually return to the SideNav and create a nice full search which we will call Advanced Search. But, for the time being, let’s see if we can once again piggy back off of our nice clean architecture and quickly implement the Quick Search feature by adding filterColumn and filterValue parameters throughout the layers of the web application.

Note: The pathway I am using for development in this module is: C:\Development\FredsCars\FullStack\Module29.
Table Of Contents
  1. Modify ApiResult
  2. Modify the Vehicles controller
  3. Test the API with Postman
    • Postman Test 1
    • Postman Test 2
  4. Update the Angular Material Module
  5. Modify the Vehicles component TypeScript
  6. Modify the Vehicles HTML Template
    • Using MatFormField
    • Using MatInput
    • Modify the MatFormField styling
  7. Run the project
  8. Filter Performance Issues
  9. What's Next

Modify ApiResult

Open the ApiResult.cs file and make the modifications below in bold blue font.

FredsCarsAPI/Data/ApiResult.cs

using Microsoft.EntityFrameworkCore;
using System.Reflection;
using System.Linq.Dynamic.Core;

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,
            string? sortColumn = null,
            string? sortOrder = null,
            string? filterColumns = null,
            string? filterValue = null
        )
        {
            // filtering
            string[]? filterColumnArray = null;
            if (!string.IsNullOrEmpty(filterColumns))
            {
                filterColumnArray = filterColumns.Split(',');
            }
            
            if (!string.IsNullOrEmpty(filterColumns)
                && !string.IsNullOrEmpty(filterValue)
                && filterColumnArray.ToList().All(fc => IsValidProperty(fc))
            )
            {
                string queryString = string.Empty;
                foreach (var fc in filterColumnArray)
                {
                    queryString += string.Format("{0}.StartsWith(@0) || ",
                        fc);
                }
                queryString = queryString.Substring(0, queryString.Length - 4);
                source = source.Where(queryString, filterValue);
            }
            
            // get record count
            var count = await source.CountAsync();

  /*** existing code ***/

In the code above we added two parameters to the CreateAsync() factory method of ApiResult; filterColumns and filterValue both of type nullable string.

The filterColumns parameter will receive a comma delineated string of the columns to filter against such as “make,model”. And the filterValue will receive the value to filter by such as “Jeep” for the make or “Compass” for the model.

We add the filtering logic at the top of the method body. The filtering logic comes before the sorting and paging logic because we want to sort and page on the filtered data set.

Once we get into the body of the factory method, the first thing we need to do is convert the incoming filtersColumn string into an array of filter columns. We use the JavaScript string.Split() method to split the comma delineated string into a string array called filterColumnArray. But first we also check to make sure filterColumns is not null or empty before attempting the conversion.

string[]? filterColumnArray = null;
if (!string.IsNullOrEmpty(filterColumns))
{
	filterColumnArray = filterColumns.Split(',');
}

Within the filter logic we first check to make sure that both fitlerColumns (again) and filterValue are not null or empty. We also need to guard here against SQL Injection like we did for sorting. So we add one more condition to the if statement.

&& filterColumnArray.ToList().All(fc => IsValidProperty(fc))

In the above expression we are cleverly able to loop through the filterColunmArray using the LINQ ToList.All() method and pass to it a lambda expression that passes each filter column (such as “make” and “model”) to the IsValidProperty() method and make sure both are indeed properties of Type T; in this case T being Vehicle.

If all three conditions in the if statement are true we proceed, otherwise we skip the filtering if statement block and continue down to sorting.

We then need to modify the IQueryable<T> source variable using the IQueryable<T>.Where() method and pass to it a string.Format() expression as another Dynamic LINQ query just as we did for sorting.

source = source.Where(queryString, filterValue);

The Dynamic LINQ Where() method takes two parameters; queryString and filterValue. queryString is the formatted string for the Dynamic LINQ query and filterValue will fill the parameter placeholder in formatted string. There is actually quite a bit of build up to get the formatted string just right in the queryString variable for the Dynamic LINQ query.

string queryString = string.Empty;
foreach (var fc in filterColumnArray)
{
	queryString += string.Format("{0}.StartsWith(@0) || ",
		fc);
}
queryString = queryString.Substring(0, queryString.Length - 4);
source = source.Where(queryString, filterValue);

In the code above we create a string variable called querystring to hold the formatted string as we build it up. Next we have a foreach block where we loop through each filter column in the filterColumnArray variable and in each iteration concatenate to the current querystring variable the string:

"{0}.StartsWith(@0) || "

In each iteration the string.Format() expression also has as a parameter fc or the filterColumn name of that iteration and replaces the “{0}” placeholder with it.

Once the application exits the block the querystring will look something like:

"make.StartsWith(@0) || model.StartsWith(@0) || "

In C# this reads as the value of the column “make” starts with the value of the “{@0} parameter OR the value of the column “model” starts with the value of the “{@0}” parameter.

The next line removes the extra logical OR pipes from the end of the queryString.

queryString = queryString.Substring(0, queryString.Length - 4);

Finally, we are able to modify the IQueryable<T> source variable with the IQueryable<T>.Where() method passing to it the queryString we built up and the filterValue variable as a parameter to fill every instance of the “{@0}” parameter placeholder in the foramat string. If we had more then one parameter to pass we would need one more than one parameter placeholder in the format string such as (@0), (@1), (@2).

The end result of the queryString for the Dynamic LINQ query will look similar to the following:

'make.StartsWith("Jeep") || model.StartsWith("Jeep")'

Or

'make.StartsWith("Compass") || model.StartsWith("Compass")'

Modify the Vehicles controller

Open the VehiclesController.cs file and modify it with the contents below.

FredsCarsAPI/Controllers/VehiclesController.cs

/*** existing code ***/

        [HttpGet]
        public async Task<ApiResult<VehicleDTO>> GetVehicles(
            int pageIndex = 0, int pageSize = 10,
            string? sortColumn = null,
            string? sortOrder = null,
            string? filterColumns = null,
            string? filterValue = null)
        {
            // get Vehicles page
            var dataQuery = _vehicleRepo.Vehicles.AsNoTracking()
                .Include(v => v.VehicleType)
                .ConvertVehiclesToDTOs();

            return await ApiResult<VehicleDTO>.CreatAsync(
                dataQuery,
                pageIndex,
                pageSize,
                sortColumn,
                sortOrder,
                filterColumns,
                filterValue);
        }
    }
}

In the code above we added filterColumns and filterValue to the incoming parameter list of the HTTP GET method GetVehciles(). We then pass those values along with the IQueryable datasource, paging variables, and sort variables to the ApiResult factory method and return the result.

Test the API with Postman

Start a debugging instance of the Web API by right clicking on the FredsCarsAPI project and selecting “Debug --> Start New Instance“.

Postman Test 1

Create a GET request with the following URL and click Send.

https://localhost:40443/api/Vehicles?pageIndex=0&pageSize=5&sortColumn=status&sortOrder=desc&filterColumns=make,model&filterValue=Jeep

In the screenshot above I have sent a request for the first page sorted on status in descending order and filtered on the ‘Make’ and ‘Model’ columns to give me back all Vehicles with a Make or Model value of ‘Compass’.

Here is the complete JSON result.

{
    "data": [
        {
            "id": 9,
            "status": "New",
            "year": "2022",
            "make": "Jeep",
            "model": "Compass",
            "color": "White",
            "price": 34980,
            "vin": "3C4NJDFB5NT114024",
            "vehicleType": "Jeep"
        },
        {
            "id": 10,
            "status": "New",
            "year": "2022",
            "make": "Jeep",
            "model": "Compass",
            "color": "Red",
            "price": 39275,
            "vin": "3C4NJDCB1NT118172",
            "vehicleType": "Jeep"
        }
    ],
    "pageIndex": 0,
    "pageSize": 5,
    "totalCount": 2,
    "totalPages": 1
}

In the JSON results above you can see that there is a totalCount of 2 and both objects have a model value of Compass.

I’m pretty confident this is accurate because when I look at the data in SSOX (SQL Server Object Explorer) in Visual Studio there are only two records with a model type of Compass.

Postman Test 2

Create another GET request with the following URL and click Send.

https://localhost:40443/api/Vehicles?pageIndex=0&pageSize=5&sortColumn=status&sortOrder=desc&filterColumns=make,model&filterValue=Jeep

In the screenshot above I have sent a request for the first page sorted on status in descending order and filtered on the ‘Make’ and ‘Model’ columns to give me back all Vehicles with a Make or Model value of ‘Jeep’.

Here is the complete JSON result.

{
    "data": [
        {
            "id": 9,
            "status": "New",
            "year": "2022",
            "make": "Jeep",
            "model": "Compass",
            "color": "White",
            "price": 34980,
            "vin": "3C4NJDFB5NT114024",
            "vehicleType": "Jeep"
        },
        {
            "id": 10,
            "status": "New",
            "year": "2022",
            "make": "Jeep",
            "model": "Compass",
            "color": "Red",
            "price": 39275,
            "vin": "3C4NJDCB1NT118172",
            "vehicleType": "Jeep"
        },
        {
            "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": 0,
    "pageSize": 5,
    "totalCount": 4,
    "totalPages": 1
}

In the JSON results above there is a totalCount of 4. And all four objects have a make value of Jeep.

Again I can verify this by looking in SSOX (SQL Server Object Explorer) in Visual Studio where I can see four records with a make value of Jeep.

It looks as though the new filtering capability is working fairly well for now. We’ll have a chance to unit test this feature soon enough.

Update the Angular Material Module

Before we update the Angular Vehicles component, we’re going to need to bring in a couple more Angular Material components that we will be needing. Modify the angular-material.module.ts file with the contents below.

FredsCars/src/app/angular-material.module.ts

import { NgModule } from "@angular/core";
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatTableModule } from '@angular/material/table';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';

const angularMatModules = [
  MatToolbarModule,
  MatIconModule,
  MatButtonModule,
  MatTableModule,
  MatProgressSpinnerModule,
  MatPaginatorModule,
  MatSortModule,
  MatSidenavModule,
  MatCheckboxModule,
  MatFormFieldModule,
  MatInputModule
]

@NgModule({
  imports: [
    angularMatModules
  ],
  exports: [
    angularMatModules
  ]
})
export class AngularMaterialModule { }

In the above code we have registered the MatFormField and MatInput Angular Material components so we can use them to dress up the input field where the user enters their filter value and keep the UI consistant.

Modify the Vehicles component TypeScript

Open the vehicles.component.ts file and make the modifications shown below in bold blue font.

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'];

  defaultPageIndex: number = 0;
  defaultPageSize: number = 10;
  public defaultSortColumn: string = "id";
  public defaultSortOrder: "asc" | "desc" = "asc";
  defaultFilterColumns: string = "make,model";
  filterValue?: string;

  @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() {
    // moved get vehicles to load data
    //  so MatSort can call multiple times as user
    //  clicks a new sort column.
    this.loadData();

    // get vehcileTypes and transform into search categories
    //  for sidenav (leave in ngOnitInit since we only need to
    //  do this once)
    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));
  }

  loadData(filterValue?: string) {
    // get vehicles
    var pageEvent = new PageEvent();
    pageEvent.pageIndex = 0;
    pageEvent.pageSize = 5;
    this.filterValue = filterValue;
    this.getVehicleData(pageEvent);
  }
  
  getVehicleData(event: PageEvent) {
    // get rid of third empty click state for MatSort
    if (this.sort && this.sort.direction == "") {
      this.sort.direction = "asc";
    }

    var url = environment.baseUrl + 'api/vehicles';
    var params = new HttpParams()
      .set("pageIndex", event.pageIndex.toString())
      .set("pageSize", event.pageSize.toString())
      .set("sortColumn", (this.sort)
        ? this.sort.active
        : this.defaultSortColumn)
      .set("sortOrder", (this.sort)
        ? this.sort.direction
        : this.defaultSortOrder);

    if (this.filterValue) {
      params = params
        .set("filterColumns", this.defaultFilterColumns)
        .set("filterValue", this.filterValue);
    }

    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.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 ***/
}

In the code above, the first thing we did was add two properties to the VehiclesComponent class:

  • defaultFilterColumns of type string with the value “make, model”
  • filterValue of type nullable string: used to capture the user input for filtering.

Next we modified the signature of the loadData() method to take in a nullable string parameter called filterValue. Within the loadData() method body we assign the incoming filterValue parameter’s value to the filterValue property of the class.

The final modification comes in the getVehicleData() method. While building up the HTTP parameters, we use an if statement to check if the filterValue property of the class has a value or not. If so, we set two new HttpParam values with the HttpParams.set() method.

  • filterColumns: gets set to defaultFilterColumns class property with a value of “make,model”.
  • filterValue: gets set to filterValue class property.

Modify the Vehicles HTML Template

Open the vehicles.component.html file and modify the code with the bold blue font shown below.

FredsCars/src/app/vehicles/vehicles.component.html

/*** existing code ***/
  
  <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>

    <mat-form-field [ngClass]="{'hide-results' : vehicles.data.length <= 0}">
      <mat-label>Quick Search</mat-label>
      <input matInput #filter (keyup)="loadData(filter.value)"
             placeholder="Filter by Make or Model" />
    </mat-form-field>

    <table mat-table [dataSource]="vehicles"
           matSort
           [matSortActive]="defaultSortColumn"
           [matSortDirection]="defaultSortOrder"
           (matSortChange)="loadData(filter.value)"
           *ngIf="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>

/*** existing code ***/

In the HTML Template code above, we introduce two new Angular Material components; MatFormField and MatInput. We added a new Angular Material MatFormField component using the mat-form-field element and use the Angular ngClass attribute to hide the filter until the MatDataSource has data and the table displays with results.

Nested within the mat-form-field element we have a native input element decorated with the matInput attribute to utilize the AM MatInput component. We have also added an Angular template reference variable called #filter to the input element so we can reference the element in the TypeScript and later in the HTML.

For the final change in the Template HTML we added filter.value, which references the input element with the #filter template reference and gets its value, as a parameter to the loadData() method bound to the MatTable table element’s matSortChange event.

Using MatFormField

The definition for mat-form-field on the Angular Material Form Field overview page is the following:

<mat-form-field> is a component used to wrap several Angular Material components and apply common Text field styles such as the underline, floating label, and hint messages.

MatFormField

So without the MatInput component/input element being nested in a mat-form-field element, it would look like a basic input element with no styling at all. Which is fine, but since we are using a styling component library we can dress it up a bit and make it look nice.

The overview page goes on to say: The following Angular Material components are designed to work inside a <mat-form-field>:

  • <input matNativeControl> & <textarea matNativeControl>
  • <select matNativeControl>
  • <mat-select>
  • <mat-chip-list>

Using MatInput

The definition for matInput on the Angular Material Input overview page is the following:

matInput is a directive that allows native <input> and <textarea> elements to work with <mat-form-field>.

MatInput

You can read more about MatInput in the link above for the overview page.

Modify the MatFormField styling

We are almost there. We just need to make one tweak to the MatFormField component’s styling. Open the vehicles.component.scss file and make the following changes shown in bold blue font.

FredsCars/src/app/vehicles/vehicles.component.scss

/*** existing code ***/
mat-sidenav {
    padding-right: 10px;
}

li {
    list-style: none;
}

.mat-mdc-form-field {
    font-size: 14px;
    width: 100%;
}

Here we just bumped up the font size of the input element a little and gave it a width of 100 percent.

Run the project

Run the project in debug mode, navigate to the Vehicles page and your screen should look similar to the following.

Type the character ‘j’ in the Quick Search input field and your browser should show the following results.

In the screenshot above four vehicles matched the filter input whose Make started with ‘j’ and they are all Jeeps.

If you type “com” in the Quick Search input field, you’ll get two Vehicles back in the results with the Model name of “Compass”.

Filter Performance Issues

The filtering we have just created is a very cool feature indeed. However I don’t know if you noticed, but the way this is implemented could affect performance on the server quite extensively if you have a lot of users of the application. You see every time a user types in a character in the filter input field, the keyup() event makes a call to the server. So if they type “compass”, that search is hitting the server seven times.

But don’t worry. We will have a chance in later modules to refactor this feature and mitigate the issue.

What’s Next

In this module we were able to get a nice filtering Quick Search feature working along side our sorting and paging. In the next module we will unit test the filtering to make sure we didn’t miss anything as far as bugs or code efficiency. And then we will start to implement the Advanced Search in the SideNav.

< Prev
Next >

Leave a ReplyCancel reply

Chapter 1: Static HTML – Designing the landing page.

  • Static HTML – Designing the landing page.
  • Let’s get started!
  • Mock your site with HTML
  • Make CSS easy with Bootstrap
  • Mock your content
  • Introducing JavaScript
  • JavaScript Code Improvements
  • Results Data
  • Images and the HTML Image Element.
  • Revisiting Reusability for CSS and JavaScript
  • Reuse for HTML: PART 1
  • Reuse for HTML: PART 2
  • Details Page – Using a Bootstrap Component
  • Creating Links
  • Chapter One Conclusion

Chapter 2: ASP.Net Core – Let’s talk Dynamic

  • Introduction to ASP.Net Core
  • What is .Net?
  • What is ASP.Net
  • Introduction to Entity Framework Core

Chapter 3: ASP.Net MVC Core – Models, Views, and Controllers [ASP.Net Core v9]

  • Introduction to ASP.Net Core MVC
  • Create the project: ASP.Net Core MVC
  • Explore the ASP.Net Core Empty Web Project Template
  • Configure the Application for MVC
  • Create a Controller: Home Controller
  • Create a View: Index View for the Home Controller
  • Install Bootstrap using Libman
  • Create the Layout template
  • Create the Model
  • Install EF Core & Create the Database
  • Seed the Database: Loading test data
  • DI (Dependency Injection): Display a List of Vehicles
  • Repository Pattern: The Vehicles Repo
  • Unit Test 1: Home Controller Can Use Vehicle Repository
  • Unit Test 2: Vehicle Repository Can Return List
  • Add the ImagePath Migration and Thumbnail images to results
  • Pagination: Create a Custom Tag Helper
  • Sorting
  • Category Filter
  • Partial View: Break out the vehicle results
  • View Component: Create dynamic category buttons
  • Create the Details page
  • Create the Create Page

Chapter 7: Using Server Side & Client Side technologies together. [ASP.Net Core v7 & Angular v15]

  • Intro to Full Stack Development
  • Fred’s Cars – Full Stack Development
  • Prepare the environment
  • Create the Visual Studio Solution
  • Add the ASP.Net Core Web API project
  • Add the Angular Project
  • Wire it up!
  • WeatherForecast: Understanding the basics
  • Vehicles API Controller: Mock Data
  • Vehicles Angular Component: Consuming Data
  • Routing and Navigation
  • Using a Component Library: Angular Material
  • Our first Angular Material Component: MatToolbar
  • Configuring for Saas: CSS with superpowers
  • Create the Header & Footer components
  • Displaying Results with MatTable
  • Loading: Using a Progress Spinner
  • MatTable: Client-Side Paging and Sorting
  • MatSidenav: Create a Search Sidebar
  • MatCheckbox: Category Search UI
  • Adding an image to the welcome page
  • Create the database with Entity Framework Core migrations
  • MatPaginator & PageEvent: Custom Server-Side Paging
  • Unit Testing: Custom Server-Side Paging
  • Repository Pattern: VehicleRepository
  • Unit Test: Paging in the Vehicles controller
  • Server-Side Sorting
  • Unit Tests: Sorting
  • Filter (Quick Search)
  • Unit Tests: Filter feature
  • Advanced Search: Categories
  • Unit Tests: Search by category
  • Progress Spinner: Final Fix

TOC

  • What were WebForms?
  • Enter MVC
    • Understanding MVC
    • Advantages of MVC
  • ASP.Net Core MVC – A total rewrite
  • ASP.Net Core 2 MVC – Here come Razor Pages
    • Understanding Razor Pages
  • ASP.Net Core 3 – Dropping the MVC reference
    • Understanding Blazor
  • Dropping the MVC reference
  • Hello .Net 5!
  • What’s Next? – Here comes .Net 6.

Recent Posts

  • Angular Commands Cheat Sheet
  • Installing Git for Windows
  • Installing Postman
  • Installing SQL Server 2022 Express
  • Installing Visual Studio 2022

Recent Comments

No comments to show.

Archives

  • November 2023
  • October 2023
  • June 2023
  • October 2021

Categories

  • Angular
  • ASP.Net
  • Environment Setup
  • See All
  • SQL Server
  • Visual Studio
  • Web API & Rest Services

WordPress Theme Editor

Copyright © 2025 Web Development School.

Powered by PressBook Blog WordPress theme