Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET / ASP.NET-Core

SPA^2 using ASP.Net Core 1.1 + Angular 2.4 - part 6

5.00/5 (9 votes)
21 Mar 2017CPOL13 min read 20.8K   240  
How to use NSwag to create Typescript data models and data services for Angular 2 and to generate Swagger Web API documentation.

or from Github Part6 branch here
Note: source excludes the machine generated code below.

Introduction

So far this series of articles has been describing a way to integrate ASP.Net Core with Angular 2. 

Part 1 - How to integrate ASP.Net Core and Angular 2
Part 2 - How to use Tag Helpers to display data
Part 3 - How to use Tag Helpers for data input, added SQL backend using EF Core.
Part 4 - Adding token authentication with JWT using OpenIdDict. 
Part 5 - Adding async services, server-side validation, a simple Angular 2 datagrid & more tag helpers.

This is part 6 - Where we're adding Swagger documentation for our Web API methods using NSwag, and then using the (free) NSwag Studio tool to automatically create Typescript data models and data services for Angular 2.

Note: the topic of deployment to IIS will be held over until part 7, as this article was getting a little too long.

Adding NSwag - a Swagger library for Dot Net

First we'll add NSwag then use it create an online self-documenting test page.

We could use Postman to test the Web API methods, and personally I find this very useful for testing and debugging, however it is not going always useful semi-technical end-users, or for documentation, and using Postman is still quite manual, that is every time you alter a Web API data service, you've now created something else that needs to be maintained. The point of these articles has been in part to show another way to use ASP.Net Core with Angular 2 (or really any client side framework) but also to show how you can reduce the amount of cut/past/manual hackwork .

So first NSwag's Swagger docs.

Right click the A2SPA project, click Manage NuGet packages

Image 1

Click on Browse, the enter nswag in the search box,
and then after a second or two, click on NSwag.AspNetCore

Image 2

Click on the Install button, after a few seconds, you'll get a review changes dialogue

Click OK

Image 3

Click I Accept

Image 4

Add Nswag.Annotations using NuGet

Image 5

Next we need to update our Startup.cs file to register NSwag to provide us Swagger documentation

Just above the line:

C#
app.UseMvc(routes =>
{

Add

C#
app.UseSwaggerUi(typeof(Startup).GetTypeInfo().Assembly, new SwaggerUiOwinSettings()
{
    DefaultPropertyNameHandling = PropertyNameHandling.CamelCase
});

To resolve the dependencies, add these to the "usings" at the top of the Startup.cs class:

C#
using NJsonSchema;
using NSwag.AspNetCore;
using System.Reflection;

Let's try building this, and then hitting Ctrl-F5 .. It's not going to work, but let see how badly

Our home page is still there, manually change the URL to from …/home to …/swagger

Image 6

This message tells us NSwag thinks we have a problem with CORS, or cross origin request settings.

Image 7

Since we don't have a CORS policy (and haven't needed one so far) this is entirely possible.

But, before we go fixing something that isn't necessarily broken, let's have a look at the F12 debug, using the network tab first.

Hit F12, click on the network tab, clear the cache, then hit refresh, scroll through the various requests until you reach the swagger.json file, then view the response itself:

Image 8

We're getting the error

System.InvalidOperationException: The method 'Post' on path '/api/Home' is registered multiple times.

Since we're not looking to provide details on our Home controller, as it's not used for Web API data services, we can tell Swagger to ignore it.

Update the HomeController.cs file to add

C#
using NSwag.Annotations;

And then decorate the class with the [SwaggerIgnore] attribute, so that the final HomeController.cs looks like this:

C#
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
 
namespace A2SPA.Controllers
{
    [SwaggerIgnore]
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            ViewData["Title"] = "Home";
            return View();
        }
 
        public IActionResult Error()
        {
            return View();
        }
    }
}

Once again build and press ctrl-F5 and we'll see we're getting something of a trend here, now the PartialController needs to have the same SwaggerIgnore treatment:

Image 9

Add the same using statement and Swagger Ignore attribute, again build and hit ctrl-F5

And, as before we get another error exposed in Nswag, this time in relation to OpenIdDict's data services.

System.InvalidOperationException: The JSON property 'item' is defined multiple times on type 'AspNet.Security.OpenIdConnect.Primitives.OpenIdConnectParameter'.

As we've already got services for this, we'll ignore this too, repeat the same procedure for each of these files, AccountController.cs, AuthorizationController.cs and ResourceController.cs and then once again rebuild and press ctrl-F5 (or refresh if you still have your browser open).

Finally we should see our Swagger page, with the swagger.json no longer generating an exception, but being a fully-fledged JSON document:

Image 10

Close the debug window

Click on the link on the Swagger documentation page, where it has "SampleData" in grey text, it will expand to show all of the services available under that particular Web Api class:

Image 11

Each of these entries correspond to a different Web API method, they represent all of the new CRUD operations we added in the last article, part 5.

Click on each of (say) the Get methods and these will open up to a test page, as below:

Image 12

Those methods such as "get by id", that requires an Id, will have entry text boxes which will be used to feed the query (if required). Enter say 1 into the ID box and click the "Try it out!" button, and well see our security being enforced, as we're given a 401 error

Image 13

To verify this is indeed the issue, go to the SampleDataController.cs class, and immediately above the GetById method add the [AllowAnonymous] attribute:

C#
[AllowAnonymous]
[HttpGet("{id}")]

public async Task<IActionResult> GetById(int id)
{

Because we have the [Authorize] attribute at a class level , this change will allow just the one method, GetById to be accessible to check this is indeed the issue. Rebuild, and if you so happen to still have the browser open you could even click the same "Try it out" button that failed a moment ago (again if the web server too is still running, else hit  ctrl-F5 to launch the web server + browser), once you click the button a dotty spinner starts:

Image 14

And it all is well you should see a result  - assuming you picked a valid Id of course.

Image 15

Since we really don't want to leave this method without security, remove the [AllowAnonymous] attribute from the GetById method in the SampleDataController class

During development, you might get away with putting in the [AllowAnonymous] however there will always remain the risk of leaving this in place, in the code, and thereby failing to protect your Web API data services. Ideally NSwag could add a bearer token for us, so that we can use the OpenIdDict JWT security with our data services.

To fix this, next edit the configure method of Startup.cs  where we added the configuration earlier for Nswag, change this:

C#
app.UseSwaggerUi(typeof(Startup).GetTypeInfo().Assembly, new SwaggerUiOwinSettings()
{
    DefaultPropertyNameHandling = PropertyNameHandling.CamelCase
});

To this:

C#
app.UseSwaggerUi(typeof(Startup).GetTypeInfo().Assembly, new SwaggerUiOwinSettings()
{
    OperationProcessors =
    {
        new OperationSecurityScopeProcessor("apikey")
    },
    DocumentProcessors =
    {
        new SecurityDefinitionAppender("apikey", new SwaggerSecurityScheme
        {
            Type = SwaggerSecuritySchemeType.ApiKey,
            Name = "Authorization",
            In = SwaggerSecurityApiKeyLocation.Header
        })
    },
    DefaultPropertyNameHandling = PropertyNameHandling.CamelCase
});

Rebuild and hit ctrl-F5 to launch your browser, then log on, press F12 to get the browser debug window,

In Chrome you need to select the Application tab,

Image 16

 

In Firefox this is called the Storage tab

 

Image 17

In Edge and IE the location is a little more obscure, you press F12 for debug, then need to go to the Console (ctrl 2) is a shortcut.

Image 18

Then type  

sessionStorage

As you type, intellisense kicks in and you see the choices narrow,

Image 19

Once you get one choice hit enter twice, and then you see the session storage values. (Note: if using local storage in place of session storage, type local storage here instead)

Image 20

Expand the node:

Image 21

Once again select all of the token, this can be a little painful ctrl-A does not work, and double-clicking will only select to the nearest symbol such as a - so instead try clicking just inside the first double quote, then and drag down (it's shorter that way), but remember you will pick up the trailing double quote if you do this.

So far we've had to enter the URL manually, to get to NSwag's Swagger documentation page. Although you could use duplicate the tab (see if your browser supports this, right click on your browser tab), we'll create a menu link for this in our view.

Since the NSwag Swagger docs are going to be secured, we'll hide them from people that have not yet logged into the web application.

Go to the Views folder, to the AppComponent.cshtml page edit and add just below this code:

HTML
<li [hidden]="!isLoggedIn()">
    <a class="nav-link" (click)="setTitle('About - A2SPA')" routerLink="/about">About</a>
</li>

The following:

HTML
<li [hidden]="!isLoggedIn()">
    <a class="nav-link" (click)="setTitle('About - A2SPA')" routerLink="/about">About</a>
</li>
<li [hidden]="!isLoggedIn()">
    <a class="nav-link" href="/swagger">NSwag</a>
</li>

Notice we've used a standard hyperlink as opposed to the Angular 2  routerLink as we're navigating away from the rest of our SPA.

Once again rebuild and refresh your browser if it is already open, or hit ctr-F5 if it is not, you should see the new menu item:

Image 22

Right click the new NSwag menu entry and open this in a new tab, leaving the logged-in "About" tab open for reference. In the about tab press F12, find the sessionStorage grab a copy of the bearer token, then switch to the tab looking at NSwag's Swagger docs, click the Authorise button at the top,

Image 23

In the "value" text box enter the word Bearer followed by a space then paste the bearer token you've just copied:

Image 24

Click the Authorize button, nothing apparent happens, the window closes, but you'll now find the requests will be authorized with a bearer token.

Open up the list of operations, click Get - to get all, as we tried earlier

Image 25

Then once again click try it out:

Image 26

And finally, we have authorization and a test-bed for our developers or our end-users.

Note: if you want to limit this to developers, one quick method is to wrap the configure code in Startup.cs inside

C#
if (env.IsDevelopment())
{
    // development only code can go here
}

You can easily now see what is happening during development (which you can do of course with Postman or similar tools) except here the documentation is generated automatically around your API.

The last thing we'll add, again with view of code that does more for us, will be to expand the descriptions on our Web API methods to provide further tips and pointers to consumers of the new Swagger documentation.

Refining the Swagger documentation with NSwag

Initially our documentation may seem great, automatically created, with little or no work, but as you start adding more and more services, operations and data types, your users are likely to start asking for more information.

Consider the GetById method

Image 27

Compare this with the code that it is documenting:

C#
// GET: api/sampleData/{1}
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
    var testData = await _context.TestData
                           .DefaultIfEmpty(null as TestData)
                           .SingleOrDefaultAsync(a => a.Id == id);
 
    if (testData == null)
    {
        return Json(NoContent());
    }
 
    return Json(Ok(testData));
}

Position your cursor to the left of the [HttpGet] attribute, press enter, then up arrow then hit the forward slash key 3 times, you'll automatically create the shell of a comment:

C#
// GET: api/sampleData/{1}
/// <summary>
/// 
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
    var testData = await _context.TestData
                           .DefaultIfEmpty(null as TestData)
                           .SingleOrDefaultAsync(a => a.Id == id);
 
    if (testData == null)
    {
        return Json(NoContent());
    }
     return Json(Ok(testData));
}

You can delete the previous comment

C#
// GET: api/sampleData/{1}

And then fill in the details with something a little more meaningful, say:

C#
/// <summary>
/// Returns a single TestData record with matching Id
/// </summary>
/// <param name="id">the ID of the record to retrieve</param>
/// <returns>an IActionResult</returns>

Hit enter at the end of the </summary> closing tag, you'll create an empty line, type <r and intellisense will give you <remarks>, press enter, here you'll get an opportunity to add an further remarks.

C#
/// <remarks>This method will return an IActionResult containing the TestData record and StatusCode 200 if successful. 
/// If there is a an error, you will get a status message and StatusCode which will indicate what was the error.</remarks>

And lastly the trap I fall into all of the time. You might almost think it should work, with all we have now. Since adding these comments now means our methods have extra help when hovering or for intellisense - so at least we may be helping other developers.

NSwag will automatically use the XML documentation once there is an XML file to refer to, however this is not generated by default. And this is one of those settings typically done once and then forgotten about, that is actually turning on XML documentation.

In VS2015 right click the project, then click properties, and then select "Build", then at the bottom of the properties, ensure that there is a tick in the checkbox "XML Documentation File"

Image 28

Similarly, if using VS2017 right click the project, then click properties, and then select "Build", and as with VS2015, at the bottom of the properties, ensure the checkbox "XML Documentation File" is ticked:

Image 29

Now rebuild, hit ctrl-F5 or refresh your swagger docs page, and you should see the results of our efforts:

When closed we get a comment added to the right:

Image 30

Opened, we get much more information:

Image 31

There are many more features, too many to add here without this becoming more about NSwag.

You can expand the other methods, adding documentation to the other methods as you like, or refer to the final source for further ideas.

Automatically creating the Typescript data models and services for Angular2

Before we turn our attention to deployment, while still working with NSwag and in the automation mindset, one last piece is automation of the Typescript data models and Typescript data services used in for our Angular 2 client side code.

As we've generated the Swagger docs, you will have seen the Swagger formatted JSON data, for example:

http://localhost:57149/swagger/v1/swagger.json

The swagger.json file is generated by NSwag as it looks over your Web API classes, and then consumed in the swagger client which you see when you navigate to ../swagger path noted earlier. 

This same swagger.json file can also be used by the NSwag Studio tool. NSwag Studio is also from Rico Sutter, creator/maintainer of NSwag, and can be installed from here:

The above link is from the GitHub repo of NSwag here: https://github.com/NSwag/NSwag

Once installed, grab the URL of the swagger.json file from your swagger page:

Image 32

The in NSwag Studio, select the Documents Tab on the left, then "Swagger Specification", paste in the swagger.json URL you just copied from your swagger page. The click the "Create Local Copy" button to fetch a copy of the JSON data into NSwag Studio:

Image 33

Now on the right hand side of NSwag Studio,

Image 34

Check the box marked "TypeScript Client", you will then see a new tab appear:

Image 35

Select the TypeScript Client tab, change the Typescript version from 1.8 to 2.0:

Image 36

Next scroll down, and select "Angular 2" from the choices in the "Template" drop down.

Image 37

We'll leave the defaults as-is for now,

Image 38

Click Generate Outputs:

Image 39

This is fine if you want to cut and paste back into your source, but a better way is to set up NSwag Studio so tht it writes back directly into your Visual Studio project.

To generate the typescript file directly, at the bottom of the typescript settings page click on the ".." button to the right of the "Output file path:" entry box. Navigate to you’re the  wwwroot\app folder within your project's file space. Then add the name (say) nswag to create nswag.ts

Image 40

Click the Save button then click Generate Files. Back in Visual Studio, right click the app folder, then select Add, and then Existing Item.

Image 41

Pick out the wwwroot\app\nswag.ts file

Image 42

Next to use our generated file, in place of the existing files. Update /wwwroot/app/app.module.ts fie and change these sections of code:

From existing:

JavaScript
import { SampleDataService } from './services/sampleData.service';
import { TestData } from './models/testData';

To the new:

JavaScript
import { SampleDataClient, TestData, SwaggerException } from './nswag';

From:

JavaScript
constructor(private sampleDataService: SampleDataService, private toastrService: ToastrService) { }

To:

JavaScript
constructor(private sampleDataService: SampleDataClient, private toastrService: ToastrService) { }

The change the names of our data service calls (intellisense will hint here), from these:

JavaScript
this.sampleDataService.addSampleData(this.testData)
 … 
this.sampleDataService.getSampleData()
 … 
this.sampleDataService.editSampleData(this.testData)
 … 
this.sampleDataService.deleteRecord(itemToDelete)

To these:

JavaScript
this.sampleDataService.post(this.testData)
…
this.sampleDataService.get()
…
this.sampleDataService.put(this.testData)
…
this.sampleDataService.delete(itemToDelete.id)

Save, clean and rebuild the project, then hit ctrl-F5 and voila, it should work as before:

Image 43

Links

For further information on NSwag:

NSwag source on GitHub here: https://github.com/NSwag/NSwag
NSwag Sample source on GitHub here: https://github.com/NSwag/Samples
and author and maintainer of NSwag, Rico Suter, his blog here: http://blog.rsuter.com/

Points of Interest

(Q) Did you learn anything interesting/fun/annoying while writing the code?
(A) Reminded again of the annoying US spelling for some words. And how "Authorization" tends to work better than "Authorisation" - when sending tokens, funnily enough, it doesn't get Authorised er Authorized. 

(Q) Did you do anything particularly clever or wild or zany?
(A) Love the code generation features of NSwag and NSwag studio ... why do code by hand when you can automatically generate or re-generate it, and have this from one place - your data model.
 

History

Part 1 - How to integrate ASP.Net Core and Angular 2
Part 2 - How to use Tag Helpers to display data
Part 3 - How to use Tag Helpers for data input, added SQL backend using EF Core.
Part 4 - Adding token authentication with JWT using OpenIdDict. 
Part 5 - Adding async services, server-side validation, a simple Angular 2 datagrid & more tag helpers.

This is part 6 - Where we're adding Swagger documentation for our Web API methods using NSwag, and then using the (free) NSwag Studio tool to automatically create Typescript data models and data services for Angular 2.

Part 7 will cover deployment of our SPA / Single Page Application to IIS. 

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)