Using NSwag to Generate Angular Client for an ASP.NET Core 3 API

This week we are going to add an Angular project that will utilize the API we created a few weeks ago. This post is part of the revamp of my ASP.NET Core Basics repo that I kicked off when .NET Core 3.0 was released. For details on how we got to the current point in the application check out the following posts.

Swagger/OpenAPI with NSwag and ASP.NET Core 3
ASP.NET Core 3: Add Entity Framework Core to Existing Project
New Razor Pages Project Backed with an API
Using NSwag to Generate Angular Client for an ASP.NET Core 3 API
Using NSwag to Generate React Client for an ASP.NET Core 3 API
Using NSwag to Generate Blazor Server Client for an ASP.NET Core 3.1 API
Using NSwag to Generate a Vue Client for an ASP.NET Core 3.1 API

Do note that I realize that using an ASP.NET Core backed Angular project for this sample is overkill and a plain Angular application would have been all that is needed, but I wanted to use the ASP.NET Core template as a base for all the projects in this series. After the initial application creation, you can think of this example as setting up access to a secondary API in addition to the application’s main API if that helps.

The sample code before any of the changes in this post can be found here.

API Changes

Before we get to the actual Angular side of this post we are going to update the associated API to accept all cross-origin resource sharing (CORS) requests. The ASP.NET Core setup has a lot of options and I recommend being me explicit about what your API will accept if you can. Check out the official Microsoft CORS docs for more information.

All the changes needed will be in the Startup class of the API project. The CORS setup is policy-based and each policy needs a name that I stored in the following class level constant.

private const string AllowAllCors = "AllowAll";

In the ConfigureServices function add the following to register the CORS policy. Again be more restrictive with your policy if you can but for this example, we are opening up the API to allow any request.

services.AddCors(options =>
                 {
                     options.AddPolicy(AllowAllCors,
                                       builder =>
                                       {
                                           builder.AllowAnyHeader();
                                           builder.AllowAnyMethod();
                                           builder.AllowAnyOrigin();
                                       });
                 });

Finally, in the Configure function add the following to get CORS added to the HTTP pipeline processing. I’m not 100% sure if it matters where you added it in the pipeline, but I added it close to the front. I included a bit pipeline in the sample app for reference.

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.UseCors(AllowAllCors);

app.UseHttpsRedirection();

That is all the changes needed for the API, next we will create a new Angular project.

Create an Angular Project

Add a new directory for the application and then in a terminal navigate to that directory. Then the following command can be used to create the new Angular project. The target framework isn’t required, but I have a preview of .NET Core 3.1 installed and I wanted to make sure this project is targeting .NET Core 3.0.

dotnet new angular -f netcoreapp3.0

Next, use the following command to add the new project to the solution file which is in the root of the repo. Your filenames and paths could vary if you can’t using the same code of course.

dotnet sln ..\..\BasicsRefresh.sln add ContactsAngular.csproj

Use NSwagStudio to Generate Angular Client

NSwag provides multiple options for client generation including a CLI option, code, and a Windows application. This post is going to use the Windows application which is called NSwagStudio. Download and install NSwagStudio from here.

Next, make sure your API is running and get the URL of its OpenAPI/Swagger specification URL. For example, I am using a local instance of my API and the URL I need is https://localhost:5001/swagger/v1/swagger.json. If you are using the Swagger UI you can find a link to your swagger.json under the API title.

Now that we have the OpenAPI/Swager specification URL for the API switch over to NSwagStudio. The application will open with a new document ready to go. There are a few options we will need to set. First, select the OpenAPI/Swagger Specification tab and enter your API’s specification URL in the Specification URL box.

In the Outputs section check the TypeScript Client checkbox and then select the TypeScript Client tab. There are a lot of options to play with, but I highlighted the ones I changed to generate the client for this sample. First I entered a Module name so the generated code will all be in that module. For Template, it is very important to select Angular so that the resulting code will be set up for Angular’s dependency injection. Also, make sure and change the Injection token type to InjectionToken if you are on a newer version of Angular. The final option that needs to be set is the Output file path and this is the location you want the generated file to be. I output the Angular project directory under ClientApp\src\app\apis\contactApi.ts. After all the options are set click Generate Files.

For more information on generating Angular clients check the official docs on the subject.

Create UI and Use Generated Client

The sample API is for contact management so the UI we are going to build is to display a contact list. The new components are going to go in a new contacts directory under ClientApp\src\app. In this directory, we will need two new files one for the HTML, contact-list.component.html, and the other for the backing TypeScript class, contact-list.component.ts.

The following is the full content on the HTML file. I’m not going to go into the Angular specific bit in this post, but even if you don’t know Angular you will get the idea of what is going on. The rendered result will be a table of contacts.

<h1 id="tableLabel">Contacts</h1>

<p *ngIf="!contacts"><em>Loading...</em></p>

<table class='table table-striped' aria-labelledby="tableLabel" *ngIf="contacts">
  <thead>
    <tr>
      <th>Name</th>
      <th>Address</th>
      <th>City</th>
      <th>State</th>
      <th>Postal Code</th>
      <th>Phone</th>
      <th>Email</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let contact of contacts">
      <td>{{ contact.name }}</td>
      <td>{{ contact.address }}</td>
      <td>{{ contact.city }}</td>
      <td>{{ contact.state }}</td>
      <td>{{ contact.postalCode }}</td>
      <td>{{ contact.phone }}</td>
      <td>{{ contact.email }}</td>
    </tr>
  </tbody>
</table>

Next is the TypeScript class for the contact list. This is where you get to see the usage of the client that NSwag generated. The related lines are highlighted.

import { Component } from '@angular/core';
import { contacts as Contacts } from "../apis/contactApi";

@Component({
  selector: 'app-contact-list',
  templateUrl: './contact-list.component.html'
})
export class ContactListComponent {
  public contacts: Contacts.IContact[];

  constructor(contactsClient : Contacts.ContactsClient) {
    contactsClient.getContacts().subscribe(result => {
        this.contacts = result;
      },
      error => console.error(error));
  }
}

As you can see from the code above we are injecting the ContactClient and then calling its getContracts and assigning the results to the class’s local contacts variable.

Now that our components are built we need to register them with the application. These changes are in the app.module.ts file found in the ClientApp\src\app directory. The following is the full file excluding the imports that were existing with the changes for our contact related items highlighted.

import { ContactListComponent } from "./contacts/contact-list.component";
import { contacts } from "./apis/contactApi";

@NgModule({
  declarations: [
    AppComponent,
    NavMenuComponent,
    HomeComponent,
    CounterComponent,
    FetchDataComponent,
    ContactListComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
    HttpClientModule,
    FormsModule, 
    RouterModule.forRoot([
      { path: '', component: HomeComponent, pathMatch: 'full' },
      { path: 'contact-list', component: ContactListComponent },
      { path: 'counter', component: CounterComponent },
      { path: 'fetch-data', component: FetchDataComponent },
    ])
  ],
  providers: [contacts.ContactsClient],
  bootstrap: [AppComponent]
})
export class AppModule { }

The first change was to import both the ContactListComponent and contacts which is the contact client. Next, the ContactListComponent was added to the declarations array. Then we added the ContactListComponent to the RouterModule so that Angular will know how to get us to the contact list page. Finally, we added the ContractsClient to the providers array which will allow Angular to inject the client into our contact list component.

To add our contact list to the nav menu we need to change the nav-menu.component.html file in the ClientApp\src\app\nav-menu directory. Add the following list item to add a Contact link in the navbar.

<li class="nav-item" [routerLinkActive]="['link-active']">
  <a class="nav-link text-dark" [routerLink]="['/contact-list']"
    >Contacts</a
  >
</li>

Wrapping Up

I’m repeating myself, but NSwag’s client generation makes it very simple to get starting consuming APIs, but even over time being able to regenerate a client for an API and have any changes in that API ready to go is really nice.

The sample projects after all the changes in this post can be found here.


Also published on Medium.

2 thoughts on “Using NSwag to Generate Angular Client for an ASP.NET Core 3 API”

  1. i ran into an issue where the instruction shown here work in angular dev build but not in prod. When the –prod flag is set, i receive the following error: Cannot read property ‘request’ of undefined. This only happens when a ModuleName is defined in the nswag generator.

Leave a Reply to Eric Cancel Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.