Create Gateway Service with .NET

15 MINUTE EXERCISE

In this lab you will learn about .NET and how you can build microservices using ASP.NET principles. During this lab you will create a scalable API Gateway that aggregates Catalog and Inventory APIs.

CoolStore Architecture

What is .NET (previously .NET Core)?

.NET

.NET (Core) is a is a free, open-source development platform for building many kinds of apps, from web, serverless, mobile, desktop and console apps. With .NET your code and project files look and feel the same no matter which type of app you’re building and you have access to the same runtime, API and language capabilities with each app. You can create .NET apps for many operating systems, including Windows, Linux and MacOS on a variety of hardware. .NET lets you use platform-specific capabilities, such as operating system APIs. Examples are Windows Forms and WPF on Windows .NET is open source, using MIT and Apache 2 licenses .NET is a project of the .NET Foundation.

.NET supports a number of programming faces and development environments, but today we will be looking at C# inside OpenShift Dev Spaces.

We will also be using the standard web server pattern provided by ASP .NET libraries for creating non-blocking web services.

.NET Gateway Project

The gateway-dotnet project has the following structure which shows the components of the project laid out in different subdirectories according to ASP .NET best practices:

Gateway Project

This is a minimal ASP .NET project with support for asynchronous REST services.

Examine 'Startup.cs' class in the /projects/workshop/labs/gateway-dotnet/ directory.

See how the basic web server is started with minimal services, health checks and a basic REST controller is deployed.

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors();

    services.AddControllers().AddJsonOptions(options=>
    {
            options.JsonSerializerOptions.IgnoreNullValues = true;
    });

    services.AddHealthChecks();
    services.AddControllersWithViews();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{

    ProductsController.Config();

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

    app.UseCors(builder => builder
            .AllowAnyOrigin ()
            .AllowAnyHeader ()
            .AllowAnyMethod ());

    app.UseHealthChecks("/health");

    app.UseRouting();
    app.UseDefaultFiles();
    app.UseStaticFiles();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });

}

Examine 'ProductsController.cs' class in the /projects/workshop/labs/gateway-dotnet/Controllers directory.

[ApiController]
[Route("api/[controller]")] (1)
public class ProductsController : ControllerBase
{
    [HttpGet]
    public IEnumerable<Products> Get()
    {
        private static HttpClient catalogHttpClient = new HttpClient(); (4)
        private static HttpClient inventoryHttpClient = new HttpClient();

        try
        {
            // get the product list
            IEnumerable<Products> productsList = GetCatalog(); (2)

            // update each item with their inventory value
            foreach(Products p in productsList) (3)
            {
                Inventory inv = GetInventory(p.ItemId);
                if (inv != null)
                    p.Availability = new Availability(inv);
            }

            return productsList;
        }
        catch(Exception e)
        {
            Console.WriteLine("Using Catalog service: " + catalogApiHost + " and Inventory service: " + inventoryApiHost);
            Console.WriteLine("Failure to get service data: " + e.Message);
            // on failures return error
            throw e;
        }
    }

    private IEnumerable<Products> GetCatalog()
    {
        var data = catalogHttpClient.GetStringAsync("/api/catalog").Result;
        return JsonConvert.DeserializeObject<IEnumerable<Products>>(data);
    }

    private Inventory GetInventory(string itemId)
    {
        var data = inventoryHttpClient.GetStringAsync("/api/inventory/" + itemId).Result;
        return JsonConvert.DeserializeObject<Inventory>(data);
    }

}
1 Not unlike the Quarkus and Spring boot apps previously built, the ProductsController has a single defined REST entrypoint for GET /api/products
2 In this case the Get() service first requests a list of products from the Catalog microservice
3 It then steps through each in turn to discover the amount of product in stock. It does this by calling the Inventory service for each product.
4 By using an HttpClient class for each service, .NET will efficiently manage the connection handling.

The location or binding to the existing Catalog and Inventory REST services is injected at runtime via environment variables.

Deploy on OpenShift

It’s time to build and deploy your service on OpenShift.

As you did previously for the Inventory and Catalog services in the earlier chapters, you need to create a new Component and then Push it in to the OpenShift cluster

  • IDE Task

  • CLI

Click on 'Terminal' → 'Run Task…​' and execute the 'Gateway' tasks in the correct order.

Che - RunTask

Execute the following commands in the terminal window

cd /projects/workshop/labs/gateway-dotnet

Then execute the odo commands like you did for Catalog service

Do not forget to push your new component
To open a terminal window, click on 'Terminal' → 'New Terminal'
Unlike Catalog and Inventory service, you are going to push the 'gateway-dotnet/' folder as an input to OpenShift. OpenShift will build the .NET .dll file from the project file and source code for you.

Once this completes, your application should be up and running. OpenShift runs the different components of the application in one or more pods which are the unit of runtime deployment and consists of the running containers for the project.

Test your Service

In the OpenShift Web Console, from the Developer view, click on the 'Open URL' icon of the Gateway Service

OpenShift - Gateway Topology

Your browser will be redirect on your Gateway Service running on OpenShift.

Gateway Service

Then click on 'Test it'. You should have an array of json output like this but with many more products.

Look in the json to see how the Gateway service has combined the product information together with the inventory (quantity) information:

[ {
  "itemId" : "329299",
  "name" : "Red Fedora",
  "desc" : "Official Red Hat Fedora",
  "price" : 34.99,
  "availability" : {
    "quantity" : 35
  }
},
...
]

Service discovery

You might be wondering how the Gateway service knows how to contact the Catalog and Inventory services. As discussed earlier, these values are injected as environment variables at runtime. You can see the code that consumes those variables below. This is from ProductsController.cs class in the /projects/workshop/labs/gateway-dotnet/ directory.

The REST URLs are built from the environment variables:-

public static void Config()
{
    try
    {
        // discover the URL of the services we are going to call
        catalogApiHost = "http://" +
            Environment.GetEnvironmentVariable("CATALOG_COOLSTORE_SERVICE_HOST") + ":" +
            Environment.GetEnvironmentVariable("CATALOG_COOLSTORE_SERVICE_PORT");

        inventoryApiHost = "http://" +
            Environment.GetEnvironmentVariable("INVENTORY_COOLSTORE_SERVICE_HOST") + ":" +
            Environment.GetEnvironmentVariable("INVENTORY_COOLSTORE_SERVICE_PORT");

        // set up the Http conection pools
        inventoryHttpClient.BaseAddress = new Uri(inventoryApiHost);
        catalogHttpClient.BaseAddress = new Uri(catalogApiHost);
    }

You can inspect the variables available in the Gateway service pod by selecting the Gateway service and then the running Pod.

Gateway Service

You can look at the environment variables by selecting the Pod Terminal.

Gateway Service

Now you can inspect the runtime environment by executing this shell command:

env | grep COOLSTORE

There are values added for all the services in the current project. We have highlighted a couple that are used by the code to access the Catalog Service.

Well done! You are ready to move on to the next lab.