Tutorials / Build a REST API with C# and .NET · Lesson 34 of 34

Frontend

On this page

    The Report Screen

    The owner does not only manage single books. Once a week they want the summary sheet: for each category, how many titles, how many copies, and what the stock is worth. The API already has that sheet, at GET /reports/stock, but today it is a line of JSON in a terminal. This lesson puts it on a page.

    Our goal: a second page that shows the stock report, and a link to it from the Books page.

    Here is the route:

    1. Write the report page. It is the first page again, with a different address and different columns.
    2. Link the two pages to each other.
    3. Change a book, and watch the report follow.

    This lesson leaves out how the page looks (it is ugly on purpose, as before) and who is allowed to see the report. For now everyone can. Logging in, and keeping the report from some users, come later in the course.

    Where we are

    This is the solution from the end of the Books screen. One file is new and one gains a line:

    BookShop/
      src/
        BookShop.Api/
          wwwroot/
            index.html                     <- changes (one link)
            report.html                    <- NEW
    

    Only the files that change are listed. Run every command from the solution folder, BookShop/. No project is added, so there is no solution map. Keep the app running (dotnet run --project src/BookShop.Api). The pages are only files, so you refresh the browser after each edit.


    Two pages, two requests

    Each page is its own file with its own script, and asks the API for its own data. Nothing is shared between them:

    Two pages, two requests index.html the Books page report.html the report page The app GET /books GET /reports/stock a link

    So the report page is written the same way as the first page, and it can be short.


    The report page

    Create src/BookShop.Api/wwwroot/report.html. The whole file is:

    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>Stock report</title>
    </head>
    <body>
      <h1>Stock report</h1>
    
      <p><a href="/">Books</a></p>
    
      <p id="message"></p>
    
      <table>
        <thead>
          <tr>
            <th>Category</th>
            <th>Titles</th>
            <th>Copies</th>
            <th>Stock value</th>
          </tr>
        </thead>
        <tbody id="report-rows"></tbody>
      </table>
    
      <script>
        async function loadReport() {
          const response = await fetch('/reports/stock');
          if (!response.ok) {
            document.getElementById('message').textContent =
              'Could not load the report. The API answered ' + response.status + '.';
            return;
          }
    
          const lines = await response.json();
          const rows = document.getElementById('report-rows');
    
          for (const line of lines) {
            const row = rows.insertRow();
            row.insertCell().textContent = line.category;
            row.insertCell().textContent = line.titles;
            row.insertCell().textContent = line.copies;
            row.insertCell().textContent = line.stockValue.toFixed(2);
          }
        }
    
        loadReport();
      </script>
    </body>
    </html>
    

    Compare it with the first page. The shape is identical: ask, check response.ok, read the JSON, add a row per item. Only four things differ:

    • The address is /reports/stock.
    • The names are the four values of one report line, as the stock report lesson named them, in lowercase because that is how they come out in JSON: category, titles, copies, stockValue.
    • toFixed(2) is on the stock value, for the same reason as the price: the API sends 181.0, and money should read 181.00.
    • There is no replaceChildren(). This page loads its data once, when it opens, so there is no old content to clear.

    A line of the report is a different thing from a book: it has no id and no author, and nothing to edit. So there are no buttons and no form. This page only reads.

    The new page is not called index.html, so UseDefaultFiles() does not apply, and its address includes the name. Open http://localhost:5101/report.html. For the five books of our shop, the page shows:

    Category Titles Copies Stock value
    Cooking 2 6 181.00
    Programming 3 22 852.30

    Every number can be traced to the table. Programming has three books, with 12, 7 and 3 copies, so 22 copies. The value is 12 × 39.90 + 7 × 34.50 + 3 × 44.00 = 478.80 + 241.50 + 132.00 = 852.30. The two cooking books give 4 + 2 = 6 copies and 4 × 29.00 + 2 × 32.50 = 116.00 + 65.00 = 181.00. (A book with an empty category would appear as a line of its own, first in the list, with a blank first cell. We ran it: a book with category '' and 1 copy at 10.00 gives the line | 1 | 1 | 10.00. The report lesson showed why.)

    The page does no sums. It receives finished totals and shows them. The adding up is done by the database, in the query behind the endpoint. Keeping the work on the server means there is one place to get a number wrong, and the page stays a reader.


    Link the two pages

    A visitor needs a way from one page to the other. In HTML that is a link: the <a> element ("anchor"), with its href (the address to go to). The report page already has its link back, <a href="/">Books</a>; the address / is the Books page. The Books page needs the way forward. Open index.html and add one line under the heading. The rest of the file is exactly as it was at the end of the last lesson, so only the lines around the change are shown:

    <body>
      <h1>Books</h1>
    
      <p><a href="report.html">Stock report</a></p>            <!-- new -->
    
      <p id="message"></p>
      <ul id="problems"></ul>
    

    Refresh the Books page and click the link. The browser loads a different page, and the other page's script runs from the start. Click Books to come back.


    Change a book, watch the report

    The two pages never talk to each other. Each one asks the API when it opens. So a change made on the Books page shows on the report the next time the report loads.

    Try it, from the five original books. On the Books page, Edit Clean Code and change its stock from 7 to 9, then Save. Now open the report page. Programming has changed and Cooking has not:

    Category Titles Copies Stock value
    Cooking 2 6 181.00
    Programming 3 24 921.30

    Two more copies of a book that costs 34.50 add 2 copies and 69.00, so 22 + 2 = 24 copies and 852.30 + 69.00 = 921.30. (If you changed other books in the last lesson, your numbers differ, and the same arithmetic explains them.)

    The report page was not told about the change. It asked again, and the API answered from the database. A report page that stays open while books change would not notice, and neither would a second Books page in another tab. You have to refresh. Pages that update by themselves exist, and we leave them out.


    Where we ended

    Compare with your solution. A second page, and a link:

    BookShop/
      src/
        BookShop.Api/
          wwwroot/
            index.html                     the Books screen, now with a link to the report
            report.html                    the stock report: one table, read only
    

    The screens of the system are done: Books, with add, edit and delete, and the stock report. Anyone who can reach the address can use both.


    Recap

    • A second page is a second file in wwwroot with its own script and its own request. Nothing is shared between pages.
    • The report page has the same shape as the first page, with another address, other field names (category, titles, copies, stockValue) and no buttons, since it only reads.
    • A page that loads its data once has nothing to clear. The page that reloads its list (Books) clears first.
    • The page shows the totals. The adding up is done on the server, in one place.
    • An <a href="..."> link takes the visitor to another page, whose script then starts fresh.
    • A page shows the data as it was when it loaded. To see a change made elsewhere, load it again.

    Questions to think about

    Each question has its answer folded away underneath. Guess first, then open it to check.

    1. Why does the report page not add up the stock values itself?
    Show answer

    The server already computes them, in one place. A page that did its own sums would be a second place to get a number wrong.

    1. You change a book on the Books page. Why does the report page not update by itself?
    Show answer

    Each page asks the API only when it loads. Nothing tells the report page about the change, so it shows the new totals the next time it loads.

    1. How is the report page different from the Books page, and why?
    Show answer

    It has no form and no buttons, because a report line is not something you add, edit or delete. It only reads.

    1. At the moment anyone who knows the address can open the report. Is that what a real shop wants?
    Show answer

    Probably not. The owner may want only managers to see the stock value. Keeping some pages for some people needs logging in, which comes later in the course.

    Next lesson: what a JWT is, the first step towards logging in.

    ← The Books Screen