Showing posts with label JSON. Show all posts
Showing posts with label JSON. Show all posts

Monday, November 9, 2026

Scaffold Interactive-Server Blazor CRUD pages with SQLite

In this walkthrough, we will use the scaffolding tool to create the CRUD pages for a data driven Interactive-Server Blazor app. The app connects directly with SQLite database using Entity Framework Core.

Overview

Blazor is a framework for developing interactive client-side web applications using C# instead of JavaScript.

Architecture

The architecture of the application that we will be building will look like this: 

ASP.NET Core hosts the server-side app and sets up SignalR endpoints where clients connect. SignalR is responsible for updating the DOM on the client with any changes. The Blazor application on the server connects directly to the database using Entity Framework Core.

Blazor Render Modes

NameDescriptionRender LocationInteractive
Static ServerStatic server-side rendering (static SSR)Server❌
Interactive ServerInteractive server-side rendering (interactive SSR) using Blazor Server.Server✔️
Interactive WASMClient-side rendering (CSR) using Blazor WebAssembly.Client✔️
Interactive AutoInteractive SSR using Blazor Server initially and then CSR on subsequent visits after the Blazor bundle is downloaded.Server, then client✔️

To apply a render mode to a component, use the @rendermode directive on the component instance or on the component definition. Example:

<Dialog @rendermode="InteractiveServer" />

In this post, we will build a Server-side Blazor app talks directly to a SQLite database. This is a very realistic option since both blazor and the database server run on the server.

Let's get coding

In a terminal window, go to a work directory. Enter the following command to create a Server-Side Blazor application inside a directory named BlazorEF: 

dotnet new blazor -o BlazorEF
cd BlazorEF
mkdir Models
mkdir Data

Open the BlazorEF folder in Visual Studio Code.

When you run the app, your default browser will load a page that looks like this: 

We will extend the above application so that it talks to SQLite using Entity Framework Core. To this end, we will be dealing with a very simple Student model. Therefore, add a Student.cs class file to the Models folder with the following C# content: 

public class Student {
    public int StudentId { get; set; }

    [Required(ErrorMessage = "You must enter first name.")]
    public String? FirstName { get; set; }

    [Required(ErrorMessage = "You must enter last name.")]
    public String? LastName { get; set; }

    [Required(ErrorMessage = "You must enter school.")]
    public String? School { get; set; }

    [Required(ErrorMessage = "You must enter gender.")]
    public String? Gender { get; set; }

    public DateOnly? DateOfBirth { get; set; }
}

Since we will be using SQLite, we will need to add the appropriate Entity Framework Core packages to our project. From within a terminal window at the root of your BlazorEF project, run the following commands to add the appropriate database related packages: 

dotnet add package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design

We need to add a connection string for the SQLite database. Add the following to the appsettings.json file: 

"ConnectionStrings": {
  "DefaultConnection": "DataSource=college.db;Cache=Shared;"
}

We will be using the Entity Framework Code First approach. 

Developers prefer having sample data when building data driven applications. Therefore, we will create some sample data to ensure that our application behaves as expected. Student data from this link will be used.

The starting point is to create a database context class. Add a C# class file named SchoolDbContext.cs in the Data folder with the following C# code: 

public class SchoolDbContext : DbContext {
    public DbSet<Student> Students => Set<Student>();

    public SchoolDbContext(DbContextOptions<SchoolDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Student>().HasData(GetStudents());
    }

    private static IEnumerable<Student> GetStudents() {
        const string studentsUrl =
            "https://gist.githubusercontent.com/medhatelmasry/2ae7a36d5392f862b0d1039b2a52b9b4/raw/45533eda6c43ce439dbc65c37a7fbdef01546c2d/students.json";

        using var httpClient = new HttpClient();

        var json = httpClient.GetStringAsync(studentsUrl).GetAwaiter().GetResult();

        return System.Text.Json.JsonSerializer.Deserialize<List<Student>>(json)
            ?? throw new InvalidOperationException("The students JSON returned no data.");
    }
}

Notice the above code will seed Student data into the database.

In the Program.cs file, just before ‘var app = builder.Build();’, add the following code so that our application can use SQLite:

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

builder.Services.AddDbContext<SchoolDbContext>(
    options => options.UseSqlite(connectionString)
);

We are now ready to apply Entity Framework migrations, create the database and seed data. If you have not done so already, you will need to globally install the Entity Framework CLI tool. This tooling is installed globally on your computer by running the following command in a terminal window:

dotnet tool install --global dotnet-ef

To make sure nothing is broken, build your project with "dotnet build" before proceeding. Then, from within a terminal window inside the BlazorEF root directory, run the following command to create migrations: 

dotnet ef migrations add M1 -o Data/Migrations

 This results in the creation of a migration file ending with the name ....M1.cs in the Data/Migrations folder. In my case, this file looked like this: 

Note that the above code also includes commands for inserting sample data.

The next step is to create the SQLite college.db database file. To automatically apply migrations, add the following code to Program.cs, right before the final app.Run() statement:

using (var scope = app.Services.CreateScope()) {
  var services = scope.ServiceProvider;

  var context = services.GetRequiredService<SchoolDbContext>();    

  context.Database.Migrate();
}

We will scaffold the CRUD pages for students using the scaffold tool. To install the scaffold tool globally, enter this command in any terminal window:

dotnet tool install --global Microsoft.dotnet-scaffold

Thereafter, run the scaffold tool in the root folder of your Blazor project with:

dotnet scaffold 

Follow these steps:

Delete this unnecessary addition in the appsettings.json file:

"SchoolDbContext": "Data Source=SchoolDbContext.db"

You will notice these razor components have been added to your Blazor project:

Add this menu item to your Components/Layout/NavMenu.razor file.

<div class="nav-item px-3">
    <NavLink class="nav-link" href="students">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Students
    </NavLink>
</div>

Run your application:

You will discover that Student CRUD operations work seamlessly.

I hope you learned something new in this tutorial and trust that you will build much more sophisticated Blazor apps than this.

Sunday, January 2, 2022

Google charts with DataTable .NET Wrapper and API data in ASP.NET 6.0 Razor Pages App

You have data from an API (or any other data source like a database) and wish to display the results in a chart. The library we will use for generating charts is the freely available Google Charts JavaScript-based API. The Google DataTable .NET Wrapper is used to create a lightweight representation of the google.visualization.DataTable object directly in Microsoft.NET. The wrapper allows for the creation of the appropriate JSON which is easily ingested by the Google Chart Tools JavaScript library.

I will show you how to generate six types of charts to display dynamically generated data. The source of data will be an API at https://northwind.vercel.app/api/orders that displays orders. I will work with the ASP.NET Razor Pages template (AKA Web App).

Source code:  https://github.com/medhatelmasry/OrdersChartRazorGoogleWrapper

Companion Video: https://youtu.be/Ie43mv57-5o

The environment I am using is: https://github.com/medhatelmasry/OrdersChartRazorGoogleWrapper

  • .NET version 6.0.100
  • Visual Studio Code

The orders API

We will work with the orders API at https://northwind.vercel.app/api/orders. The data in the API is generated from the well known Northwind sample SQL Server database. If you point your browser to the above address, you will see the following:



Since some properties are not useful in this tutorial, we will ignore orderDate, requiredDate, shippedDate, postalCode and details.

Also, note that shipAddress is represented by a sub JSON address object.

Project setup

Run the following command to create an ASP.NET Core Razor Pages application using .NET 6.0 in a folder named OrdersChartRazorGoogleWrapper:

dotnet new razor -f net6.0 -o OrdersChartRazorGoogleWrapper

Change directory into the new folder and open the project inside VS Code with the following commands:

cd OrdersChartRazorGoogleWrapper 

code .


Install the Google DataTable .NET Wrapper Nuget package:

dotnet add package Google.DataTable.Net.Wrapper

Address & Order model classes

Create a folder named Models. Add to the Models folder two class files, namely: Address.cs and Order.cs. 

The Address class looks like this:

public class Address {
    [JsonPropertyName("street")]
    public string? Street { get; set; }


    [JsonPropertyName("city")]
    public string? City { get; set; }


    [JsonPropertyName("region")]
    public string? Region { get; set; }


    [JsonPropertyName("country")]
    public string? Country { get; set; }

}

The Order class looks like this:

public class Order {
    [JsonPropertyName("id")]
    public int Id { get; set; }


    [JsonPropertyName("customerId")]
    public string? CustomerId { get; set; }


    [JsonPropertyName("employeeId")]
    public int? EmployeeId { get; set; }


    [JsonPropertyName("shipVia")]
    public int? ShipVia { get; set; }

    
    [JsonPropertyName("freight")]
    public decimal? Freight { get; set; }


    [JsonPropertyName("shipName")]
    public string? ShipName { get; set; }


    [JsonPropertyName("shipAddress")]
    public Address ShipAddress { get; set; } = null!;
}

Reading data

In the Pages folder, add two files ChartData.cshtml and ChartData.cshtml.cs.

Content of ChartData.cshtml is:

@page
@model ChartDataModel

Content of ChartData.cshtml.cs is:

using System.Text.Json;
using Google.DataTable.Net.Wrapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using OrdersChartRazorGoogleWrapper.Models;

namespace OrdersChartRazorGoogleWrapper.Pages;

public class ChartDataModel : PageModel {
    private readonly ILogger<ChartDataModel> _logger;

    public ChartDataModel(ILogger<ChartDataModel> logger) {
        _logger = logger;
    }

    public async Task<IActionResult> OnGet() {
        Order[] orders = await GetOrdersAsync();

        var data = orders
          .GroupBy(_ => _.ShipAddress.City)
          .Select(g => new
          {
              Name = g.Key,
              Count = g.Count()
          })
          .OrderByDescending(cp => cp.Count)
          .ToList();


        //let's instantiate the DataTable.
        var dt = new Google.DataTable.Net.Wrapper.DataTable();
        dt.AddColumn(new Column(ColumnType.String, "Name", "Name"));
        dt.AddColumn(new Column(ColumnType.Number, "Count", "Count"));

        foreach (var item in data) {
            Row r = dt.NewRow();
            r.AddCellRange(new Cell[] {
              new Cell(item.Name),
              new Cell(item.Count)
            });
            dt.AddRow(r);
        }

        //Let's create a Json string as expected by the Google Charts API.
        return Content(dt.GetJson());
    }

    private async Task<Order[]> GetOrdersAsync() {
        HttpClient client = new HttpClient();
        var stream = client.GetStreamAsync("https://northwind.vercel.app/api/orders");
        var orders = await JsonSerializer.DeserializeAsync<Order[]>(await stream);

        return orders!;
    }
}


The above code in ChartData.cshtml.cs returns a JSON representation of  Google.DataTable.Net.Wrapper.DataTable. It contains data from the Orders API representing the number of orders by city.

At this stage, let's run our web application and verify that we are indeed able to read data from the Orders API and subsequently generate JSON data. Run your application with:

dotnet watch run

Point your browser to https://localhost:7205/chartdata

NOTE: you will need to adjust the port number to suit your environment.

This is what was revealed in my browser:


We have a sense of assurance that our data is ready to be displayed in a chart.

Charting the data

Let's first generate a simple column-chart. Replace your Pages/Index.cshtml with the following code:

@page
@model IndexModel
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>

<div id="column_chart_div"></div>
<script type="text/javascript">

    google.charts.load('current', {
        packages: ['corechart', 'bar']
    });
    google.setOnLoadCallback(drawChart);

    function drawChart() {
        var jsonData = $.ajax({
            url: '/ChartData',
            dataType: "json",
            async: false
        }).responseText;
         // Create our data table out of JSON data loaded from server.
        var data = new google.visualization.DataTable(jsonData);
        var options = { title: 'Orders by city' };
        var chart = new google.visualization.ColumnChart(document.getElementById('column_chart_div'));
        chart.draw(data, options);
    }

</script>

Point your browser to the home page, you should see a column-chart as follows:


If you want to see more types of charts, replace Pages/Index.cshtml with the following code:

@page
@model IndexModel

<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>

<div id="column_chart_div"></div>
<div id="line_chart_div"></div>
<div id="pie_chart_div"></div>
<div id="area_chart_div"></div>
<div id="bar_chart_div"></div>
<div id="pie_chart_3d_div"></div>


<script type="text/javascript">
    google.charts.load('current', {
        packages: ['corechart', 'bar']
    });

    google.setOnLoadCallback(drawChart);

    function drawChart() {
        var jsonData = $.ajax({
            url: '/ChartData',
            dataType: "json",
            async: false
        }).responseText;

        PopulationChart(jsonData, "column-chart");
        PopulationChart(jsonData, "line-chart");
        PopulationChart(jsonData, "pie-chart");
        PopulationChart(jsonData, "area-chart");
        PopulationChart(jsonData, "bar-chart");
        PopulationChart(jsonData, "pie-chart-3d");
    }

    function PopulationChart(jsonData, chart_type) {
        // Create our data table out of JSON data loaded from server.
        var data = new google.visualization.DataTable(jsonData);
        var chart;
        var options = { title: 'Orders by city' };

        switch (chart_type) {

            case "line-chart":
                chart = new google.visualization.LineChart(document.getElementById('line_chart_div'));
                break;
            case "pie-chart":
                chart = new google.visualization.PieChart(document.getElementById('pie_chart_div'));
                break;
            case "area-chart":
                chart = new google.visualization.AreaChart(document.getElementById('area_chart_div'));
                break;
            case "bar-chart":
                chart = new google.visualization.BarChart(document.getElementById('bar_chart_div'));
                break;
            case "pie-chart-3d":
                options.is3D = true;
                chart = new google.visualization.PieChart(document.getElementById('pie_chart_3d_div'));
                break;
            default:
                chart = new google.visualization.ColumnChart(document.getElementById('column_chart_div'));
                break;
        }

        chart.draw(data, options);
        return false;
    }

</script>    

You should see six charts on the home page, namely: column, line, pie, area, bar and pie 3D charts.




Conclusion

It is very easy and inexpensive (free) to use Google Charts to generate charts in an ASP.NET Razor application. The .NET Google DataTable wrapper (Google.DataTable.Net.Wrapper) makes it even easier.

Wednesday, December 29, 2021

Generate PDF reports from API data using iText 7 Core library in ASP.NET Razor Pages 10.0

 PDF stands for "Portable Document Format". It is, indeed, the standard for exchanging formatted documents on the internet. PDF documents are read by Adobe Acrobat Reader, most browsers, and even some popular word processors like Microsoft Word. It is a common used-case to generate PDF reports from live data. In this tutorial, I shall show you how you can easily generate a PDF report in an ASP.NET Core Razor Pages app and display data that originates from a Products API. We shall use the iText 7 library to accomplish this task. 

Source Code: https://github.com/medhatelmasry/RazorPdfDemo
Companion Video: https://youtu.be/5KxxRbApoRY

Pre-requisites:

  • .NET version 10
  • Visual Studio Code

The products API

We will work with the products API at https://northwind.vercel.app/api/products. The data in the API is generated from the well known Northwind sample SQL Server database. If you point your browser to the above address, you will see the following:

Project setup

Run the following command to create an ASP.NET Core Razor Pages application using .NET 10.0 in a folder named RazorPdfDemo:

dotnet new razor -f net10.0 -o RazorPdfDemo

Change directory into the new folder and open the project inside VS Code with the following commands:

cd RazorPdfDemo

code .

Add the iText 7 Core package so that we have the ability to generate PDF output.

dotnet add package itext7
dotnet add package itext.bouncy-castle-adapter

Product model class

Each Product JSON object contains the following properties:


Id (int)
SupplierId (int)
CategoryId (int)
QuantityPerUnit (string)
UnitPrice (decimal)
UnitsInStock (short)
ReorderLevel (short)
Discontinued (bool)
Name (string)

We need to create a Product model class that represents the above JSON object. Therefore create a Models folder and add to it a Product class with the following content:

public partial class Product {
  [JsonPropertyName("id")]
  public int Id { get; set; } 
  [JsonPropertyName("supplierId")]
  public int? SupplierId { get; set; }
  [JsonPropertyName("categoryId")]
  public int? CategoryId { get; set; }
  [JsonPropertyName("quantityPerUnit")]
  public string? QuantityPerUnit { get; set; }
  [JsonPropertyName("unitPrice")]
  public decimal? UnitPrice { get; set; }
  [JsonPropertyName("unitsInStock")]
  public short? UnitsInStock { get; set; }
  [JsonPropertyName("unitsOnOrder")]
  public short? UnitsOnOrder { get; set; }
  [JsonPropertyName("reorderLevel")]
  public short? ReorderLevel { get; set; }
  [JsonPropertyName("discontinued")]
  public bool Discontinued { get; set; }
  [JsonPropertyName("name")]
  public string Name { get; set; } = null!;
}

Generating PDF report

In the Pages folder, create two new files named Report.cshtml and Report.cshtml.cs. 

Add the following C# code to Report.cshtml.cs:

using iText.Kernel.Colors;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas.Draw;
using iText.Layout;
using iText.Layout.Element;
using iText.Layout.Properties;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPdfDemo.Models;
using System.Text.Json;

namespace RazorPdfDemo.Pages;

public class ReportModel : PageModel {
  private readonly ILogger<IndexModel> _logger;

  public ReportModel(ILogger<IndexModel> logger) {
    _logger = logger;
  }

  public async Task<IActionResult> OnGet() {
    MemoryStream ms = new MemoryStream();

    PdfWriter writer = new PdfWriter(ms);
    PdfDocument pdfDoc = new PdfDocument(writer);
    Document document = new Document(pdfDoc, PageSize.A4, false);
    writer.SetCloseStream(false);

    Paragraph header = new Paragraph("Northwind Products")
      .SetTextAlignment(TextAlignment.CENTER)
      .SetFontSize(20);

    document.Add(header);

    Paragraph subheader = new Paragraph(DateTime.Now.ToShortDateString())
      .SetTextAlignment(TextAlignment.CENTER)
      .SetFontSize(15);
    document.Add(subheader);

    // empty line
    document.Add(new Paragraph(""));

    // Line separator
    LineSeparator ls = new LineSeparator(new SolidLine());
    document.Add(ls);

    // empty line
    document.Add(new Paragraph(""));

    // Add table containing data
    document.Add(await GetPdfTable());

    // Page Numbers
    int n = pdfDoc.GetNumberOfPages();
    for (int i = 1; i <= n; i++) {
      document.ShowTextAligned(new Paragraph(String
        .Format("Page " + i + " of " + n)),
        559, 806, i, TextAlignment.RIGHT,
        VerticalAlignment.TOP, 0);
    }

    document.Close();
    byte[] byteInfo = ms.ToArray();
    ms.Write(byteInfo, 0, byteInfo.Length);
    ms.Position = 0;

    FileStreamResult fileStreamResult = new FileStreamResult(ms, "application/pdf");

    //Uncomment this to return the file as a download
    //fileStreamResult.FileDownloadName = "NorthwindProducts.pdf";

    return fileStreamResult;
  }

  private async Task<Table> GetPdfTable() {
      // Table
      Table table = new Table(4, false);

      // Headings
      Cell cellProductId = new Cell(1, 1)
         .SetBackgroundColor(ColorConstants.LIGHT_GRAY)
         .SetTextAlignment(TextAlignment.CENTER)
         .Add(new Paragraph("Product ID"));

      Cell cellProductName = new Cell(1, 1)
         .SetBackgroundColor(ColorConstants.LIGHT_GRAY)
         .SetTextAlignment(TextAlignment.LEFT)
         .Add(new Paragraph("Product Name"));

      Cell cellQuantity = new Cell(1, 1)
         .SetBackgroundColor(ColorConstants.LIGHT_GRAY)
         .SetTextAlignment(TextAlignment.CENTER)
         .Add(new Paragraph("Quantity"));

      Cell cellUnitPrice = new Cell(1, 1)
         .SetBackgroundColor(ColorConstants.LIGHT_GRAY)
         .SetTextAlignment(TextAlignment.CENTER)
         .Add(new Paragraph("Unit Price"));

      table.AddCell(cellProductId);
      table.AddCell(cellProductName);
      table.AddCell(cellQuantity);
      table.AddCell(cellUnitPrice);

      Product[] products = await GetProductsAsync();

      foreach (var item in products) {
        Cell cId = new Cell(1, 1)
            .SetTextAlignment(TextAlignment.CENTER)
            .Add(new Paragraph(item.Id.ToString()));

        Cell cName = new Cell(1, 1)
            .SetTextAlignment(TextAlignment.LEFT)
            .Add(new Paragraph(item.Name));

        Cell cQty = new Cell(1, 1)
            .SetTextAlignment(TextAlignment.RIGHT)
            .Add(new Paragraph(item.UnitsInStock.ToString()));

        Cell cPrice = new Cell(1, 1)
            .SetTextAlignment(TextAlignment.RIGHT)
            .Add(new Paragraph(String.Format("{0:C2}", item.UnitPrice)));

        table.AddCell(cId);
        table.AddCell(cName);
        table.AddCell(cQty);
        table.AddCell(cPrice);
      }

      return table;
  }

  private async Task<Product[]> GetProductsAsync() {
    HttpClient client = new HttpClient();
    var stream = client.GetStreamAsync("https://northwind.vercel.app/api/products");
    var products = await JsonSerializer.DeserializeAsync<Product[]>(await stream);
    
    return products!;
  }
}

What does the above code do?
  1. The GetProductsAsync() method makes an HTTP GET request to endpoint https://northwind.vercel.app/api/products and reads products data. It then de-serializes the data into an array of Product objects and subsequently returns the array.
  2. The GetPdfTable() method does the following:
    • The heading of the table is created. There will be four columns with titles: Product ID, Product Name, Quantity and Unit Price
    • An array of Product objects is obtained from a call to the GetProductsAsync() method
    • We iterate through each item in the array and add rows of data to the table
  3. The OnGet() method does the following:
    • The first five lines in the OnGet() method sets up all the objects that are needed to generate a PDF document.
    • A header with title "Northwind Products" is placed at the top of the report - center aligned.
    • A sub-header with the current date is placed under the heading - also center aligned.
    • This is followed by an empty line, a solid-line, and another empty line.
    • The table containing product data is then displayed.
    • Paging is added to the top right-side of each page
    • Finally, the report is streamed down to the browser.
Add the following code to Report.cshtml:

@page
@model ReportModel

Let us add a menu item to the navigation of our web app. Open Pages/Shared/_Layout.cshtml and add the following markup code to the bottom of the <ul> .... </ul> navigation block:

<li class="nav-item">
  <a class="nav-link text-dark" asp-area="" asp-page="/Report">Products PDF</a>
</li>

At this stage, let's run our web app and verify that we are indeed able to read data from the Northwind products API and subsequently generate a PDF report. Run your application with:

dotnet watch run

Point your browser to https://localhost:7292

NOTE: you will need to adjust the port number to suit your environment.

This is what the home page looks like:


Click on "Products PDF". You should soon after see the PDF document being generated in your browser:




You can click on the download icon on the top right-side of the report in order to download a copy of the report. 

Alternatively, you can uncomment the following code in the OnGet() method if you want the report to get directly downloaded to your computer:

// fileStreamResult.FileDownloadName = "NorthwindProducts.pdf";

When you run the app again and click on the "Products PDF" link, the report named "NorthwindProducts.pdf" gets immediately downloaded to your computer.

Conclusion

Using the iText 7 library is pretty straight forward when it comes to generating PDF documents. You can learn more at https://kb.itextpdf.com/home/it7kb/ebooks/itext-7-jump-start-tutorial-for-net.

This article is intended to provide you with a good starting point for generating PDF reports from your ASP.NET Razor Pages web apps.

Monday, April 20, 2020

Working with JSON APIs from ASP.NET MVC

Companion Video: https://youtu.be/r8stP_6V0OY
Source Code: https://github.com/medhatelmasry/ConsumeStudentsAPI

APIs can be consumed from any type of application. In your career you will consume APIs mostly from JavaScript. We can, however, consume APIs from an ASP.NET MVC application too. This is what we will be doing today. There is an online Students API that we will be working with. This API works with the following HTTP methods:
POST
insert
PUT
update
GET
read
DELETE
delete
The Students API has the following columns:
studentId
string
firstName
string
lastName
string
school
string
Let us first create an ASP.NET Core MVC application. Go into your working directory in a terminal window and execute this command:
dotnet new mvc -o ConsumeStudentsAPI
The above command will create an ASP.NET MVC web application in a directory called ConsumeStudentsAPI. Next, also in a terminal window, change directory with:
cd ConsumeStudentsAPI
We will need to use a package named Newtonsoft.Json. Therefore, execute the following command in a terminal window to add this package:
dotnet add package Newtonsoft.Json
You can continue either with Visual Studio 2019 or Visual Studio Code. It is really up to you.
Add to the Models folder a class file named Student.cs with the following class definition:
public class Student {

  [Display(Name = "ID")]
  [Key]
  public string studentId { get; set; }

  [Required]
  [Display(Name = "First Name")]
  public string firstName { get; set; }
 
  [Required]
  [Display(Name = "Last Name")]
  public string lastName { get; set; }

  [Required]
  [Display(Name = "School")]
  public string school { get; set; }
}
Notice these annotations:
·       Display allows you to have an alternative display name for a property in the model
·       Key sets studentId as the primary key
·       Required makes sure that the user enters a value for this property.
We will be using the IHttpClientFactory factory class to make HTTP requests to the API. Therefore, we need to add a singleton object into the application. Add this code to the ConfigureServices() method in Startup.cs:
services.AddHttpClient();
Next, add to the Controllers folder a file named StudentsController.cs with the following class definitions:
public class StudentsController : Controller {
  const string BASE_URL = "https://api.azurewebsites.net/";
  private readonly ILogger<StudentsController> _logger;
  private readonly IHttpClientFactory _clientFactory;
  public IEnumerable<Student> Students { get; set; }
  public bool GetStudentsError { get; private set; }
 
  public StudentsController(ILogger<StudentsController> logger, IHttpClientFactory clientFactory) {
    _logger = logger;
    _clientFactory = clientFactory;
  }

  public async Task<IActionResult> Index() {
    var message = new HttpRequestMessage();
    message.Method = HttpMethod.Get;
    message.RequestUri = new Uri($"{BASE_URL}api/students");
    message.Headers.Add("Accept", "application/json");
 
    var client = _clientFactory.CreateClient();

    var response = await client.SendAsync(message);

    if (response.IsSuccessStatusCode) {
        using var responseStream = await response.Content.ReadAsStreamAsync();
        Students = await JsonSerializer.DeserializeAsync<IEnumerable<Student>>(responseStream);
    } else {
        GetStudentsError = true;
        Students = Array.Empty<Student>();
    }

    return View(Students);
  }

  public async Task<IActionResult> Details(string id) {
    if (id == null)
      return NotFound();

    var message = new HttpRequestMessage();
    message.Method = HttpMethod.Get;
    message.RequestUri = new Uri($"{BASE_URL}api/students/{id}");
    message.Headers.Add("Accept", "application/json");

    var client = _clientFactory.CreateClient();

    var response = await client.SendAsync(message);

    Student student = null;

    if (response.IsSuccessStatusCode) {
      using var responseStream = await response.Content.ReadAsStreamAsync();
      student = await JsonSerializer.DeserializeAsync<Student>(responseStream);
    } else {
      GetStudentsError = true;
    }

    if (student == null)
      return NotFound();

    return View(student);

  }

  public IActionResult Create() {
    return View();
  }

  [HttpPost]
  [ValidateAntiForgeryToken]
  public async Task<IActionResult> Create([Bind("studentId,firstName,lastName,school")] Student student)
  {
    if (ModelState.IsValid) {
      HttpContent httpContent = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(student), Encoding.UTF8);
      httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

      var message = new HttpRequestMessage();
      message.Content = httpContent;
      message.Method = HttpMethod.Post;
      message.RequestUri = new Uri($"{BASE_URL}api/students");

      HttpClient client = _clientFactory.CreateClient();
      HttpResponseMessage response = await client.SendAsync(message);

      var result = await response.Content.ReadAsStringAsync();

      return RedirectToAction(nameof(Index));
    }

    return View(student);
  }

  public async Task<IActionResult> Edit(string id) {
    if (id == null)
      return NotFound();

    var message = new HttpRequestMessage();
    message.Method = HttpMethod.Get;
    message.RequestUri = new Uri($"{BASE_URL}api/students/{id}");
    message.Headers.Add("Accept", "application/json");

    var client = _clientFactory.CreateClient();

    var response = await client.SendAsync(message);

    Student student = null;

    if (response.IsSuccessStatusCode) {
      using var responseStream = await response.Content.ReadAsStreamAsync();
      student = await JsonSerializer.DeserializeAsync<Student>(responseStream);
    } else {
      GetStudentsError = true;
    }

    if (student == null)
      return NotFound();

    return View(student);

  }

  [HttpPost]
  [ValidateAntiForgeryToken]
  public async Task<IActionResult> Edit(string id, [Bind("studentId,firstName,lastName,school")] Student student)
  {
    if (id != student.studentId)
      return NotFound();

    if (ModelState.IsValid) {
      HttpContent httpContent = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(student), Encoding.UTF8);
      httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

      var message = new HttpRequestMessage();
      message.Content = httpContent;
      message.Method = HttpMethod.Put;
      message.RequestUri = new Uri($"{BASE_URL}api/students/{id}");

      HttpClient client = _clientFactory.CreateClient();
      HttpResponseMessage response = await client.SendAsync(message);

      var result = await response.Content.ReadAsStringAsync();

      return RedirectToAction(nameof(Index));
    }

    return View(student);
  }

  public async Task<IActionResult> Delete(string id) {
    if (id == null)
      return NotFound();

    var message = new HttpRequestMessage();
    message.Method = HttpMethod.Get;
    message.RequestUri = new Uri($"{BASE_URL}api/students/{id}");
    message.Headers.Add("Accept", "application/json");

    var client = _clientFactory.CreateClient();

    var response = await client.SendAsync(message);

    Student student = null;

    if (response.IsSuccessStatusCode) {
      using var responseStream = await response.Content.ReadAsStreamAsync();
      student = await JsonSerializer.DeserializeAsync<Student>(responseStream);
    } else {
        GetStudentsError = true;
    }

    if (student == null)
      return NotFound();

    return View(student);

  }

  [HttpPost, ActionName("Delete")]
  [ValidateAntiForgeryToken]
  public async Task<IActionResult> DeleteConfirmed(string id) {
    var message = new HttpRequestMessage();
    message.Method = HttpMethod.Delete;
    message.RequestUri = new Uri($"{BASE_URL}api/students/{id}");

    HttpClient client = _clientFactory.CreateClient();
    HttpResponseMessage response = await client.SendAsync(message);

    var result = await response.Content.ReadAsStringAsync();

    return RedirectToAction(nameof(Index));
  }
}
The above code represents a controller that has action methods to list, add, edit and delete data. We will need to have views for the action methods in StudentsContrller. Therefore, in the Views folder, create another folder named Students. Inside of the Views/Students folder add these Create.cshtml, Delete.cshtml, Details.cshtml, Edit.cshtml and Index.cshtml files:

Create.cshtml

@model ConsumeStudentsAPI.Models.Student

@{
  ViewData["Title"] = "Add Student";
}

<h1>@ViewData["Title"]</h1>

<hr />
<div class="row">
  <div class="col-md-4">
    <form asp-action="Create">
      <div asp-validation-summary="ModelOnly" class="text-danger"></div>

      <div class="form-group">
        <label asp-for="studentId" class="control-label"></label>
        <input asp-for="studentId" class="form-control" />
        <span asp-validation-for="studentId" class="text-danger"></span>
      </div>

      <div class="form-group">
        <label asp-for="firstName" class="control-label"></label>
        <input asp-for="firstName" class="form-control" />
        <span asp-validation-for="firstName" class="text-danger"></span>
      </div>

      <div class="form-group">
        <label asp-for="lastName" class="control-label"></label>
        <input asp-for="lastName" class="form-control" />
        <span asp-validation-for="lastName" class="text-danger"></span>
      </div>

      <div class="form-group">
        <label asp-for="school" class="control-label"></label>
        <input asp-for="school" class="form-control" />
        <span asp-validation-for="school" class="text-danger"></span>
      </div>

      <input type="submit" value="Create" class="btn btn-success" />
      <a asp-action="Index" class="btn btn-primary">&lt;&lt; Back to List</a>
    </form>
  </div>
</div>

Delete.cshtml

@model ConsumeStudentsAPI.Models.Student
@{
    ViewData["Title"] = "Delete Student";
}
 
<h1>@ViewData["Title"]</h1>
<h3>Are you sure you want to delete this?</h3>
<div>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.studentId)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.studentId)
        </dd> 
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.firstName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.firstName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.lastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.lastName)
        </dd>
 
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.school)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.school)
        </dd>
    </dl>   
    <form asp-action="Delete">
        <input type="hidden" asp-for="studentId" />
        <input type="submit" value="Delete" class="btn btn-danger" /> 
        <a asp-action="Index" class="btn btn-primary">&lt;&lt; Back to List</a>
    </form>
</div>

Details.cshtml

@model ConsumeStudentsAPI.Models.Student

@{
  ViewData["Title"] = "Student Details";
}

<h1>@ViewData["Title"]</h1>
<div>
  <hr />
  <dl class="row">
    <dt class="col-sm-2">
        @Html.DisplayNameFor(model => model.studentId)
    </dt>
    <dd class="col-sm-10">
      @Html.DisplayFor(model => model.studentId)
    </dd>

    <dt class="col-sm-2">
      @Html.DisplayNameFor(model => model.firstName)
    </dt>
    <dd class="col-sm-10">
      @Html.DisplayFor(model => model.firstName)
    </dd>

    <dt class="col-sm-2">
      @Html.DisplayNameFor(model => model.lastName)
    </dt>
    <dd class="col-sm-10">
      @Html.DisplayFor(model => model.lastName)
    </dd>

    <dt class="col-sm-2">
          @Html.DisplayNameFor(model => model.school)
    </dt>
    <dd class="col-sm-10">
      @Html.DisplayFor(model => model.school)
    </dd>
  </dl>
</div>
<div>
  <a asp-action="Edit" asp-route-id="@Model.studentId" class="btn btn-warning">Edit</a> 
  <a asp-action="Index" class="btn btn-primary">&lt;&lt; Back to List</a>
</div>


Edit.cshtml



@model ConsumeStudentsAPI.Models.Student

@{
  ViewData["Title"] = "Edit Student";
}

<h1>@ViewData["Title"]</h1>
<hr />
<div class="row">
  <div class="col-md-4">
    <form asp-action="Edit">
      <div asp-validation-summary="ModelOnly" class="text-danger"></div>
      <input type="hidden" asp-for="studentId" />
      <div class="form-group">
        <label asp-for="firstName" class="control-label"></label>
        <input asp-for="firstName" class="form-control" />
        <span asp-validation-for="firstName" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="lastName" class="control-label"></label>
        <input asp-for="lastName" class="form-control" />
        <span asp-validation-for="lastName" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="school" class="control-label"></label>
        <input asp-for="school" class="form-control" />
        <span asp-validation-for="school" class="text-danger"></span>
      </div>            
      <div class="form-group">
        <input type="submit" value="Save" class="btn btn-warning" />
        <a asp-action="Index" class="btn btn-primary">&lt;&lt; Back to List</a>
      </div>
    </form>
  </div>
</div>

Index.cshtml

@model IEnumerable<ConsumeStudentsAPI.Models.Student>

@{
  ViewData["Title"] = "List Students";
}

<div>
  <h1 class="display-4">@ViewData["Title"]</h1>

  <p>
      <a asp-action="Create" class="btn btn-sm btn-success">Create New</a>
  </p>

  <table class="table table-striped table-bordered">
    <tr>
      <th>@Html.DisplayNameFor(model => model.studentId)</th>
      <th>@Html.DisplayNameFor(model => model.firstName)</th>
      <th>@Html.DisplayNameFor(model => model.lastName)</th>
      <th>@Html.DisplayNameFor(model => model.school)</th>
      <th></th>
    </tr>
    @foreach (var item in Model)
    {
      <tr>
        <td>@item.studentId</td>
        <td>@item.firstName</td>
        <td>@item.lastName</td>
        <td>@item.school</td>
        <td style="text-align: center;">
          <a asp-action="Edit" asp-route-id="@item.studentId" class="btn btn-sm btn-warning">Edit</a>
          <a asp-action="Details" asp-route-id="@item.studentId" class="btn btn-sm btn-info">Details</a>
          <a asp-action="Delete" asp-route-id="@item.studentId" class="btn btn-sm btn-danger">Delete</a>
        </td>
      </tr>
      }
  </table>
</div>
We need to add the Students link to the main menu. Therefore add this <li> tag to Views/Shared/_Layout.cshtml after around line 26:
<li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a> </li>
Let us run the application and see what we have. The home page looks like this:
Click on Students. A list of students in the database will be shown:
You can try adding, editing, displaying and deleting data.