Click here to Skip to main content
15,039,809 members
Articles / Programming Languages / Typescript
Article
Posted 10 Apr 2021

Stats

3.6K views
68 downloads
6 bookmarked

How to Create an ASP.NET Core API Project with Page Rendering, TypeScript, and "require"

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
10 Apr 2021CPOL5 min read
Set up an ASP.NET Core API project, with TypeScript and "require" to render pages and and load JavaScript files in the browser.
In this article, you will learn how to create an ASP.NET Core Web API project that also includes rendering files that the browser requests. There are a few nuances to this!

Table of Contents

Introduction

In my recent articles, Adaptive Hierarchical Knowledge Management, Part I and Part II (ok, shameless self-reference), I had created an ASP.NET Core Web API project rather than an ASP.NET Core Web App project, because I was implementing an API, not a web application. None-the-less, my API includes default pages for administration and testing of the API functions, and to render those pages and the .js files, I ended up implementing a controller to return the .html and .js files, which is very much the wrong approach.

In this short article, I walk you through how to create an ASP.NET Core Web API project that also includes rendering files that the browser requests. There are a few nuances to this!

Why Not Use Swagger?

Swagger is great for generating a UI for testing endpoints. The point of this article is that it illustrates how to add web pages for situations where you want something that is a) more user-friendly and b) more complicated, like an admin page, that interacts with multiple endpoints.

Why Not Use a ASP.NET Core Web App Then?

Because there are times you don't need Razor project (one option in VS2019) nor do you want a Model-View-Controller project (another option in VS2019) and you certainly don't need Blazor (yet another option in VS2019.) You just want an API with some built-in pages that does more than Swagger but, because it's an API you're writing, doesn't require Razor, Blazor, or MVC. (Personally, I don't think a web application, regardless of the complexity, should ever "require" using one of those three options, but that's me.)

Basic Setup

So you've created a Web API project:

Image 1

and you have your spiffy controller:

C#
[ApiController]
[Route("[controller]")]
public class SpiffyController : ControllerBase
{
  [HttpGet]
  public object Get()
  {
    return Content("<b>Hello World</b>", "text/html");
  }
}

Then, following this excellent StackOverflow post:

You add these two lines to Startup.cs in the Configure method:

C#
app.UseDefaultFiles();
app.UseStaticFiles();
  • UseDefaultFiles: Setting a default page provides visitors a starting point on a site. To serve a default page from wwwroot without a fully qualified URI, call the UseDefaultFiles method -- Serve default documents
  • UseStaticFiles: Static files are stored within the project's web root directory. The default directory is {content root}/wwwroot -- Static files in ASP.NET Core

Create the folder wwwroot and put index.html in it (and whatever else you want at the top level):

Image 2

My index.html file looks like this for this article:

HTML
<!DOCTYPE html>

<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>TypeScript HTML App</title>
</head>
<body>
  <div>
    <button id="btnHi">Say hello</button>
    <div id="response"></div>
    <button id="btnGoodbye">Goodbye</button>
  </div>
</body>
</html>

and goodbye.html looks like this:

HTML
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title></title>
</head>
<body>
  <h2>Goodbye!</h2>
</body>
</html>

At this point, running the project, we see:

Image 3

and:

Image 4

So cool, we have rendering of the index.html.

Then you create a Scripts folder and add a TypeScript file (note that I put the folder directly under the project):

Image 5

which gives you this message:

Image 6

So you do that, and you also set the ECMAScript version to at least 2017:

Image 7

I don't want my .js files to go into the same Scripts folder, I want them only under wwwroot/js:

Image 8

So I go to the project properties and redirect the Typescript compiler output:

Image 9

and I see that that worked:

Image 10

But index.html doesn't know how to load the .js file that will be compiled from the TypeScript file. So we add this line in index.html:

HTML
<script src="/js/app.js"></script>

Now, my demo script file looks like this:

JavaScript
window.onload = () => {
  new App().init();
};

class App {
  public init() {
    document.getElementById("btnHi").onclick = () => this.Hi();
    document.getElementById("btnGoodbye").onclick = () => window.open('goodbye.html', '_self');
  }

  private Hi(): void {
    XhrService.get(`${window.location.origin}/Spiffy`)
      .then(xhr => {
        document.getElementById("response").innerHTML = xhr.responseText;
      });
    }
  }

  class XhrService {
    public static async get(url: string, ): Promise<XMLHttpRequest> {
    const xhr = new XMLHttpRequest();

    return new Promise((resolve, reject) => {
      xhr.onreadystatechange = () => {
        if (xhr.readyState === 4) {
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve(xhr);
          } else {
            reject(xhr);
          }
        }
      };
  
    xhr.open("GET", url, true);
    xhr.setRequestHeader("Content-Type", "application/json");

    xhr.send();
    });
  }
}

And now clicking on the buttons, we see:

Image 11

and:

Image 12

Using Require

Developers describe RequireJS as "JavaScript file and module loader". RequireJS loads plain JavaScript files as well as more defined modules. It is optimized for in-browser use, including in a Web Worker, but it can be used in other JavaScript environments, like Rhino and Node. It implements the Asynchronous Module API. Using a modular script loader like RequireJS will improve the speed and quality of your code. On the other hand, Webpack is detailed as "A bundler for javascript and friends". -- require vs. webpack

The above is a simple example, but what if my TypeScript classes are in separate files? I tend to prefer using require rather than a bundler like Webpack, if for no other reason than it is easier to configure (in my opinion) and I'm used to it.

Image 13

DO NOT DO THIS! You'll get all sorts of stuff you don't need. Do this instead:

npm install --save @types/requirejs

which installs just the d.ts file for require:

Image 14

Create an AppConfig.ts file in the Scripts folder:

JavaScript
import { App } from "./App"

require(['App'],
  () => {
    const appMain = new App();
    appMain.run();
  }
);

and refactor app.ts into two files:

App.ts

JavaScript
import { XhrService } from "./XhrService";

export class App {
  public run() {
    document.getElementById("btnHi").onclick = () => this.Hi();
    document.getElementById("btnGoodbye").onclick = () => window.open('goodbye.html', '_self');
  }

  private Hi(): void {
    XhrService.get(`${window.location.origin}/Spiffy`)
      .then(xhr => {
        document.getElementById("response").innerHTML = xhr.responseText;
      });
  }
}

XhrService.ts

JavaScript
export class XhrService {
  public static async get(url: string,): Promise<XMLHttpRequest> {
  const xhr = new XMLHttpRequest();

  return new Promise((resolve, reject) => {
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve(xhr);
        } else {
          reject(xhr);
        }
      }
    };

    xhr.open("GET", url, true);
    xhr.setRequestHeader("Content-Type", "application/json");
    
    xhr.send();
    });
  }
}

Run the Web API project, either with the project Debug property option for Launch Browser as the controller name:

Image 15

which will hit the API endpoint, or with nothing:

Image 16

Which will render index.html with the functionality implemented in App.ts.

The full project directory now looks like this:

Image 17

Debugging in Visual Studio or Chrome

The TypeScript files can be debugged in Visual Studio:

Image 18

Unfortunately, because the TypeScript .ts files are in the Scripts folder, not the wwwrooot/js folder, Chrome, when you try to set a breakpoint, displays this error:

Image 19

We can fix this by adding this line in Startup.cs:

C#
app.UseStaticFiles(new StaticFileOptions
{
  FileProvider = new PhysicalFileProvider(
  Path.Combine(env.ContentRootPath, "Scripts")),
  RequestPath = "/Scripts"
});

Now Chrome can find the .ts files and you can debug in the Chrome console:

Image 20

Conclusion

We now have a working example of:

  1. Adding TypeScript to an ASP.NET Core Web App project.
  2. Adding require so we can reference TypeScript files and do so without using a bundler.
  3. Serve the default files, like index.js
  4. Separate the TypeScript .ts files from the compiled .js files.
  5. Help Chrome's debugger find the .ts files.

And now, I can go fix my gloriosky project mentioned in the intro!

History

  • 10th April, 2021: Initial version

License

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

Share

About the Author

Marc Clifton
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
SuggestionGreat but... Pin
ZeBobo510-Apr-21 8:54
MemberZeBobo510-Apr-21 8:54 
GeneralRe: Great but... Pin
Marc Clifton11-Apr-21 2:45
mvaMarc Clifton11-Apr-21 2:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.