Named arguments in C#

I’ve actually never had or felt a need to use named arguments in C#. Am I missing anything?

Well first off I have actually used named arguments in other languages and my immediate feeling was – this makes my code so verbose and I felt I got no real benefit from them. Certainly they can (sort of) document your code a little better, but with most/all development tools including intellisense or other tools to tell the development what parameters they were editing/adding and in some cases even show the named of the argument in the editor – hence the benefit of “documenting” seemed to be of little real use.

This all said, let’s look at what we can do with named arguments.

What are named arguments?

When we write methods/functions we pass arguments/parameters using “positional arguments”, which simply means the order of arguments must match the method signature’s argument order. For example let’s look at a simple method to add a person to some application

void AddPerson(string name, string address, int age)
{
   // do something
}

So when we use this method with positional arguments we would write

AddPerson("Scooby Doo", "Mystery Machine", 11);

In C# we also get the ability to use named arguments instead (without any changes to the method signature) by including the argument name as part of the call, so for example

AddPerson(name: "Scooby Doo", address: "Mystery Machine", age: 11);

With tools like Visual Studio 2019, this doesn’t really add anything useful (if we’re mirroring the argument positions) because Visual Studio already tells us the name of each argument in the editor. Obviously outside of Visual Studio, for example is source control, maybe this is more useful.

Surely there’s more to it than that?

Positional arguments are just that, the calling code must supply each argument in the correct position and whilst we can do the same with named arguments, you can also rearrange the arguments and hence no longer need to call using the same positions, for example let’s switch around the named arguments from earlier to give us this

AddPerson(name: "Scooby Doo", age: 11, address: "Mystery Machine");

The C# compiler will simply rearrange the arguments into their positions producing the same IL as is generated for a callee using positional arguments. Here’s an example of such code generated via dotPeek – it’s exactly the same code as for the positional arguments as one would expect.

IL_0013: ldstr        "Scooby Doo"
IL_0018: ldstr        "Mystery Machine"
IL_001d: ldc.i4.s     11 // 0x0b
IL_001f: call         void NameParamsTests.Program::AddPerson(string, string, int32)
IL_0024: nop

One area where named arguments offer some extra goodness is when we’re using optional argument, so let’s assume our AddPerson signature changes to

static void AddPerson(string name, string address = null, int age = Int32.MinValue)
{
   // do something
}

If we’re using positional arguments and we don’t have an address then we must still supply a value for the address, for example

AddPerson("Scooby Doo", null, 11);

But as we’ve seen, with named arguments the order is no longer a limiting factor, therefore we can used named arguments instead and no even bother with the address, the compiler will figure it out for us, hence we can write

AddPerson(name: "Scooby", age: 11);

Note: We can ofcourse use positional and named arguments in a method call if we wish/need to but then the named arguments would need to be in the correct position limiting the usefulness of using named arguments.

Named arguments – when we have lots of arguments

The simple AddPerson method probably isn’t a great example for using named arguments, so lets instead look at a method that takes more arguments with lots of optional arguments. If we instead have a method which looks like this

void AddPerson(
   string firstName, string lastName, int age = Int32.MinValue,
   string addressLine1 = null, string addressLine2 = null, string addressLine3 = null,
   string city = null, string county = null, string postalCode = null)
{
   // do something
}

Now we can see that if we have partial details for the person we can call this method in a more succinct manner, for example

AddPerson(age: 11, firstName: "Scooby", lastName: "Doo", postalCode: "MM1");

// or with mixed positional and named arguments

AddPerson("Scooby", "Doo", 11, postalCode: "MM1");

As you’d imagine, the compiler simply handles the setting of the optional arguments etc. as before giving us IL such as

IL_0001: ldstr        "Scooby"
IL_0006: ldstr        "Doo"
IL_000b: ldc.i4.s     11 // 0x0b
IL_000d: ldnull
IL_000e: ldnull
IL_000f: ldnull
IL_0010: ldnull
IL_0011: ldnull
IL_0012: ldstr        "MM1"
IL_0017: call         void NameParamsTests.Program::AddPerson(string, string, int32, string, string, string, string, string, string)
IL_001c: nop

Once our methods start getting more arguments and especially if lots are defaulted, then named arguments start to make sense, although with a larger number of arguments, one might question whether in fact the method call itself might be in need of refactoring, with our example here we could ofcourse create separate objects for different parts of the data and with C#’s object initializer syntax we sort of get a similar way to create “named” arguments, for example

public struct Person
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public int Age { get; set; }
   public string Line1 { get; set; }
   public string Line2 { get; set; }
   public string Line3 { get; set; }
   public string City { get; set; }
   public string County { get; set; }
   public string PostalCode { get; set; }
}

void AddPerson(Person person)
{
   // do something
}

Now using object initializer syntax we could call this method like this

AddPerson(new Person
   {
      FirstName = "Scooby",
      LastName = "Doo",
      Age = 11,
      PostalCode = "MM1"
   });

Project Tye

In the last few posts I’ve been doing a lot of stuff with ASP.NET core services and clients within Kubernetes and whilst you’ve seen it’s not too hard to create a docker container/image out of services and clients, deploy to the local registry and then deploy using Kubernetes scripts, after a while you’re likely to find this tedious and want to wrap everything into a shell/batch script – an alternative is to use Project Tye.

What is Project Tye?

I recommend checking out

The basics are Project Tye can be used to take .NET projects, turn them into docker images and generate the deployments to k8s with a single command. Also Project Tye allows us to undeploy with a single command also .

Installing Project Tye

I’m using a remote Ubuntu server to run my Kubernetes cluster, so we’ll need to ensure that .NET 3.1 SDK is installed (hopefully Tye will work with 5.0 in the near future but for the current release I needed .NET 3.1.x installed.

To check your current list of SDK’s run

dotnet --list-sdks

Next up you need to run the dotnet tool to install Tye, using

dotnet tool install -g Microsoft.Tye --version "0.7.0-alpha.21279.2"

Obviously change the version to whatever the latest build is – that was the latest available as of 6th June 2021.

The tool will be deployed to

  • Linux – $HOME/.dotnet/tools
  • Windows – %USERPROFILE%\.dotnet\tools

Running Project Tye

It’s as simple as running the following command in your solution folder

tye deploy --interactive

This is the interactive version and you’ll be prompted to supply the registry you wish to push your docker images to, as we’re using localhost:32000, remember to set that as your registry, or better still we can create a tye.yaml file with configuration for Project Tye within the solution folder, here’s an example

name: myapp
registry: localhost:32000
services:
- name: frontend
  project: frontend/razordemo.csproj
- name: backend
  project: backend/weatherservice.csproj

Now with this in place we can just run

tye deploy

If you want to create a default tye.yaml file then run

tye init

Project Tye will now build our docker images, push to localhost:3200 and then generate deployments, services etc. within Kubernetes based upon the configuration. Check out the JSON schema for the Tye configuration file tye-schema.json for all the current options.

Now you’ve deployed everything and it’s up and running, but Tye also includes environment configurations, for example

env:
  - name: DOTNET_LOGGING__CONSOLE__DISABLECOLORS
    value: 'true'
  - name: ASPNETCORE_URLS
    value: 'http://*'
  - name: PORT
    value: '80'
  - name: SERVICE__RAZORDEMO__PROTOCOL
    value: http
  - name: SERVICE__RAZORDEMO__PORT
    value: '80'
  - name: SERVICE__RAZORDEMO__HOST
    value: razordemo
  - name: SERVICE__WEATHERSERVICE__PROTOCOL
    value: http
  - name: SERVICE__WEATHERSERVICE__PORT
    value: '80'
  - name: SERVICE__WEATHERSERVICE__HOST
    value: weatherservice

Just add the following NuGet package to your project(s)

<PackageReference Include="Microsoft.Tye.Extensions.Configuration" Version="0.2.0-*" />

and then you can interact with the configuration using the TyeConfigurationExtensions classes from that package. For example using the following

client.BaseAddress = Configuration.GetServiceUri("weatherservice");

Ingress

You can also include ingress configuration within your tye.yaml, for example

ingress: 
  - name: ingress  
    # bindings:
    #   - port: 8080
    rules:
      - path: /
        service: razordemo

however, as an Ingress might be shared across services/applications this will not be undeployed using the undeploy command, so not to affect potentially other applications, you can force it to be undeployed using

kubectl delete -f https://aka.ms/tye/ingress/deploy

See Ingress for more information on Tye and Ingress.

Dependencies

Along with our solution/projects we can also deploy dependencies as part of the deployment, for example if we need to also deploy a redis cache, dapr or other images. Just add the dependency to the tye.yaml like this

- name: redis
  image: redis
  bindings:
    - port: 6379

Communicating between services/applications in Kubernetes

If you have, say a service and client in a single Pod, then you’re supplied a virtual ip address and the services etc. within the Pod are accessible via localhost, but you’re more likely to want to deploy services in their own Pods (unless they are tightly coupled) to allow scaling per service etc.

How do we communicate with a services in a different Pod to our application?

A common scenario here is, we have a a client application, for example the razordemo application in the post Deploying an ASP.NET core application into a Docker image within Kubernetes and it might be using the WeatherForecast service that is created using

dotnet new webapi -o weatherservice --no-https -f net5.0

We’re not going to go into the code of the actual services apart from show the pieces that matter for communication, so let’s assumed we’ve deployed weatherservice to k8s using a configuration such as

apiVersion: apps/v1
kind: Deployment
metadata:
  name: weatherservice
  namespace: default
spec:
  selector:
    matchLabels:
      run: weatherservice
  replicas: 1
  template:
    metadata:
      labels:
        run: weatherservice
    spec:
      containers:
      - name: weatherservice
        image: localhost:32000/weatherservice
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: weatherservice
  namespace: default
  labels:
    run: weatherservice
spec:
  ports:
    - port: 80
      protocol: TCP
  selector:
    run: weatherservice

This service will exist in it’s own virtual network running on port 80, we may scale this service according to our needs and without affecting other services or clients.

If we then deploy our razordemo as per Deploying an ASP.NET core application into a Docker image within Kubernetes – it will also exist in it’s own virtual network, also running on port 80.

To communicate from razordemo to the weatherservice we simply use the service name (if we’re on the same cluster) for example http://weatherservice.

Here’s a simple example of razordemo code for getting weatherservice data…

var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("http://weatherservice");

var response = await httpClient.GetAsync("/weatherforecast");
var results = JsonConvert.DeserializeObject<WeatherForecast[]>(await response.Content.ReadAsStringAsync());

The first two lines will probably be set in your ASP.NET core Startup.cs file, although better still is for us to store the URL within configuration via an environment variable within k8s, so the client is agnostic to the service name that we deployed the weatherservice with.

Deploying an ASP.NET core application into a Docker image within Kubernetes

In the previous post we looked and an “off the shelf” image of nginx, which we deployed to Kubernetes and were able to access externally using Ingress. This post follows on from that one, so do refer back to it if you have any issues with the following configurations etc.

Let’s look at the steps for deploying our own Docker image to k8s and better still let’s deploy a dotnet core webapp.

Note: Kubernetes is deprecating its support for Docker, however this does not mean we cannot deployed docker images, just that we need to use the docker shim or generated container-d (or other container) images.

The App

We’ll create a standard dotnet ASP.NET core Razor application which you can obviously do what you wish to, but we’ll take the default implementation and turn this into a docker image and then deploy it to k8s.

So to start with…

  • Create a .NET core Razor application (mine’s named razordemo), you can do this from Visual Studio or using
    dotnet new webapp -o razordemo --no-https -f net5.0
    
  • If you’re running this on a remote machine don’t forget to change launchSettings.json localhost to 0.0.0.0 if you need to.
  • Run dotnet build

It’d be good to see this is all working, so if let’s run the demo using

dotnet run

Now use your browser to access http://your-server-ip:5000/ and you should see the Razor demo home page, or use curl to see if you get valid HTML returned, i.e.

curl http://your-server-ip:5000

Generating the Docker image

Note: If you changed launchSettings.json to use 0.0.0.0, reset this to localhost.

Here’s the Dockerfile for building an image, it’s basically going to publish a release build of our Razor application then set up the image to run the razordemo.dll via dotnet from a Docker instance.

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY razordemo.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "razordemo.dll"]

Now run docker build using the following

docker build . -t razordemo --no-cache

If you want to check the image works as expect then run the following

docker run -d -p 5000:80 razordemo 

Now check the image is running okay by using curl as we did previously. If all worked you should see the Razor demo home page again, but now we’re seeing if within the docker instance.

Docker in the local registry

Next up, we want to deploy this docker image to k8s.

k8s will try to get an image from a remote registry and we don’t want to deploy this image outside of our network, so we need to rebuild the image, slightly differently using

docker build . -t localhost:32000/razordemo --no-cache

Reference: see Using the built-in registry for more information on the built-in registry.

Before going any further, I’m using Ubuntu and micro8s, so will need to enable the local registry using

microk8s enable registry

I can’t recall if this is required, but I also enabled k8s DNS using

microk8s.enable dns

Find the image ID for our generated image using

docker images

Now use the following commands, where {the image id} was the one found from the above command

docker tag {the image id} localhost:32000/razordemo
docker push localhost:32000/razordemo

The configuration

This is a configuration based upon my previous post (the file is named razordemo.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  selector:
    matchLabels:
      run: webapp
  replicas: 1
  template:
    metadata:
      labels:
        run: webapp
    spec:
      containers:
      - name: webapp
        image: localhost:32000/razordemo
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: webapp
  labels:
    run: webapp
spec:
  ports:
    - port: 80
      protocol: TCP
  selector:
    run: webapp
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: razor-ingress
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: webapp
            port: 
              number: 80

Now apply this configuration to k8s using (don’t forget to change the file name to whatever you named your file)

kubectl apply -f ./razordemo.yaml

Now we should be able to check the deployed image, by either using the k8s dashboard or run

kubectl get ep webapp

Note the endpoint and curl to that endpoint, if all worked well you should be able to see the ASP.NET generate home page HTML and better still access http://your-server-ip from another machine and see the webpage.

Deploying and exposing an nginx server in kubernetes

In this post we’re going to create the configuration for a deployment, service and ingress controller to access an nginx instance to our network. I’ve picked nginx simply to demonstrate the process of these steps and obviously, out of the box, we get a webpage to view to know whether everything work – feel free to can change the image used to something else for your own uses.

Deployment

Below is the deployment configuration for k8s, creating a deployment named my-nginx. This will generate 2 replicas and expose the image on port 80 of the container.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80

Service

Now we’ll create the configuration for the my-nginx service

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx

Ingress

We’re going to use Ingress to expose our service to the network, alternatively we could use a load balancer, but this options requires an external load balancer, so more like used on the cloud, or we could use NodePort which allows us to assigna port to a service so that any request to the port is forwarded to the node – the main problem with this is the port may change, instead Ingress acts like a load balancer within our cluster and will allow us to configure things to route port 80 calls through to our my-nginx service as if it was running outside of k8s.

We’re going to need to enable ingress within k8s. As we’re using Ubuntu and microk8s, run the following

microk8s enable ingress

The following is the configuration for this ingress.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: http-ingress
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-nginx
            port: 
              number: 80

Running and testing

Now we need to apply the configuration to our kubernetes instance, so I’ve saved all the previously defined configuration sections into a single file named my-nginx.yaml. Now just run the following to run the configuration in k8s

kubectl apply -f my-nginx.yaml

If you like, you can check the endpoint(s) assigned within k8s using

kubectl get ep my-nginx

and then curl the one or more endpoints (in our example 2 endpoints). Or we can jump straight to the interesting bit and access your k8s host’s ip and if all worked, you’ll be directed to one of the replicates nginx instances and we should see the “Welcome to nginx!” page.

Installing Kubernetes on Ubuntu

In this post I’m just going to list the steps for installing Kubernetes (k8s) on Ubuntu server using the single node, lightweight MicroK8s.

  • If you don’t have it installed, install snap

    sudo apt install snapd
    

Now let’s go through the steps to get microk8s installed

  • Install microk8s

    sudo snap install microk8s --classic
    
  • You may wish to change permissions to save using sudo everytime, if so run the following and change {username} to your user name.

    sudo usermod -a -G microk8s {username}
    sudo chown -f -R {username} ~/.kube
    
  • Verify the installation
    sudo microk8s.status
    
  • To save us typing microk8s to run k8s command let’s alias it
    sudo snap alias microk8s.kubectl kubectl
    

Accessing the Dashboard

Whilst you can now run kubectl command, the Web based dashboard is really useful – so lets deploy it using

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.1.0/aio/deploy/recommended.yaml

By default the Dashboard can only be accessed from the local machine and from a security standpoint you may even prefer not to be running a dashboard, but if you decide you want it (for example for dev work) then…

As per Accessing Dashboard

kubectl -n kubernetes-dashboard edit service kubernetes-dashboard

Change type: ClusterIP to type: NodePort and save this file.

You’ll need to run the proxy

kubectl proxy&

Now to get the port for the dashboard, run the following

kubectl -n kubernetes-dashboard get service kubernetes-dashboard

Example output (take from Accessing Dashboard)

NAME                   TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes-dashboard   NodePort   10.100.124.90   <nodes>       443:31707/TCP   21h

You can now access the dashboard remotely now on the port 31707 (see the PORT(S) listed above), for example https://{master-ip}:31707 where {master-ip} is the server’s ip address.

To get the token for the dashboard we need to set up and few things

kubectl describe secret -n kube-system | grep deployment -A 12

Then copy the whole token to the Dashboard Token edit box and login.

To enable skipping of the requirement for a token etc. on the dashboard (should only be used on a development installation) run

kubectl edit deployment kubernetes-dashboard -n kubernetes-dashboard

then add the following line

- args:
   - --auto-generate-certificates
   - --namespace=kubernetes-dashboard
   - --enable-skip-login  # this argument allows us to skip login

Interacting with Eureka via it’s REST operations

In previous posts we’ve used Steeltoe to interact with Eureka. Obviously Steeltoe gives us good patterns for interacting with Eureka but also has the capability to work with other service discovery registries etc.

Steeltoe’s great, but what if we wanted to just write our own Eureka either for learning how things work or maybe we just want to write our own for .NET or for another language/technology?

Eureka exposes REST operations for everything you’ll need to do with a service registry, let’s take a look.

Note: I’ll give examples using localhost, obviously change to your Eureka host/ip and I’ll also show data based upon the weatherapi we’ve worked on previously.

Query for all applications and their instances

  • GET method:
    http://localhost:8761/eureka/apps
    
  • Response:
    <applications>
      <versions__delta>1</versions__delta>
      <apps__hashcode>UP_1_</apps__hashcode>
      <application>
        <name>WEATHERAPI</name>
        <instance>
          <instanceId>localhost:weatherapi:5001</instanceId>
          <hostName>localhost</hostName>
          <app>WEATHERAPI</app>
          <ipAddr>localhost</ipAddr>
          <status>UP</status>
          <overriddenstatus>UNKNOWN</overriddenstatus>
          <port enabled="false">8080</port>
          <securePort enabled="true">5001</securePort>
          <countryId>1</countryId>
          <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
            <name>MyOwn</name>
          </dataCenterInfo>
          <leaseInfo>
            <renewalIntervalInSecs>30</renewalIntervalInSecs>
            <durationInSecs>90</durationInSecs>
            <registrationTimestamp>1611512017058</registrationTimestamp>
            <lastRenewalTimestamp>1611515916921</lastRenewalTimestamp>
            <evictionTimestamp>0</evictionTimestamp>
            <serviceUpTimestamp>1611512016403</serviceUpTimestamp>
          </leaseInfo>
          <metadata class="java.util.Collections$EmptyMap"/>
          <homePageUrl>https://localhost:5001/</homePageUrl>
          <statusPageUrl>https://localhost:5001/actuator/info</statusPageUrl>
          <healthCheckUrl>https://localhost:5001/actuator/health</healthCheckUrl>
          <vipAddress>weatherapi</vipAddress>
          <secureVipAddress>weatherapi</secureVipAddress>
          <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
          <lastUpdatedTimestamp>1611512017058</lastUpdatedTimestamp>
          <lastDirtyTimestamp>1611512016217</lastDirtyTimestamp>
          <actionType>ADDED</actionType>
        </instance>
      </application>
    </applications>
    

Query for instances for a specific application id

  • GET method:
    http://localhost:8761/eureka/apps/{your app id}
    
    // Example
    http://localhost:8761/eureka/apps/weatherapi
    
  • Response:
    <application>
        <name>WEATHERAPI</name>
        <instance>
            <instanceId>localhost:weatherapi:5001</instanceId>
            <hostName>localhost</hostName>
            <app>WEATHERAPI</app>
            <ipAddr>localhost</ipAddr>
            <status>UP</status>
            <overriddenstatus>UNKNOWN</overriddenstatus>
            <port enabled="false">8080</port>
            <securePort enabled="true">5001</securePort>
            <countryId>1</countryId>
            <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
                <name>MyOwn</name>
            </dataCenterInfo>
            <leaseInfo>
                <renewalIntervalInSecs>30</renewalIntervalInSecs>
                <durationInSecs>90</durationInSecs>
                <registrationTimestamp>1611512017058</registrationTimestamp>
                <lastRenewalTimestamp>1611516936969</lastRenewalTimestamp>
                <evictionTimestamp>0</evictionTimestamp>
                <serviceUpTimestamp>1611512016403</serviceUpTimestamp>
            </leaseInfo>
            <metadata class="java.util.Collections$EmptyMap"/>
            <homePageUrl>https://localhost:5001/</homePageUrl>
            <statusPageUrl>https://localhost:5001/actuator/info</statusPageUrl>
            <healthCheckUrl>https://localhost:5001/actuator/health</healthCheckUrl>
            <vipAddress>weatherapi</vipAddress>
            <secureVipAddress>weatherapi</secureVipAddress>
            <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
            <lastUpdatedTimestamp>1611512017058</lastUpdatedTimestamp>
            <lastDirtyTimestamp>1611512016217</lastDirtyTimestamp>
            <actionType>ADDED</actionType>
        </instance>
    </application>
    

Query for instances by its instance id

  • GET method:
    http://localhost:8761/eureka/apps/{your app id}/{your instance id}
    
    // Example
    http://localhost:8761/eureka/apps/weatherapi/localhost:weatherapi:5001
    
  • Response:
    <instance>
        <instanceId>localhost:weatherapi:5001</instanceId>
        <hostName>localhost</hostName>
        <app>WEATHERAPI</app>
        <ipAddr>localhost</ipAddr>
        <status>UP</status>
        <overriddenstatus>UNKNOWN</overriddenstatus>
        <port enabled="false">8080</port>
        <securePort enabled="true">5001</securePort>
        <countryId>1</countryId>
        <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
            <name>MyOwn</name>
        </dataCenterInfo>
        <leaseInfo>
            <renewalIntervalInSecs>30</renewalIntervalInSecs>
            <durationInSecs>90</durationInSecs>
            <registrationTimestamp>1611512017058</registrationTimestamp>
            <lastRenewalTimestamp>1611517116997</lastRenewalTimestamp>
            <evictionTimestamp>0</evictionTimestamp>
            <serviceUpTimestamp>1611512016403</serviceUpTimestamp>
        </leaseInfo>
        <metadata class="java.util.Collections$EmptyMap"/>
        <homePageUrl>https://localhost:5001/</homePageUrl>
        <statusPageUrl>https://localhost:5001/actuator/info</statusPageUrl>
        <healthCheckUrl>https://localhost:5001/actuator/health</healthCheckUrl>
        <vipAddress>weatherapi</vipAddress>
        <secureVipAddress>weatherapi</secureVipAddress>
        <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
        <lastUpdatedTimestamp>1611512017058</lastUpdatedTimestamp>
        <lastDirtyTimestamp>1611512016217</lastDirtyTimestamp>
        <actionType>ADDED</actionType>
    </instance>
    

Register an application instance

  • POST method:
    Headers: “Content-Type”: “application/json”, “Accept”: “application/json”

    http://localhost:8761/eureka/apps/{your app id}
    
    // Example
    http://localhost:8761/eureka/apps/myapp
    
  • Body:

    {
      "instance": {
        "hostName": "myhost",
        "app": "myapp",
        "vipAddress": "myservice",
        "secureVipAddress": "myservice",
        "ipAddr": "10.0.0.10",
        "status": "STARTING",
        "port": {"$": "8080", "@enabled": "true"},
        "securePort": {"$": "8443", "@enabled": "true"},
        "healthCheckUrl": "http://myservice:8080/healthcheck",
        "statusPageUrl": "http://myservice:8080/status",
        "homePageUrl": "http://myservice:8080",
        "dataCenterInfo": {
          "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", 
          "name": "MyOwn"
        }
      }
    }
    
  • Response: 204 No Content

As you will see from the status we’ve set this service to STARTING and hence we need set the status to UP when our service is running. Eureka, ofcourse, is just a registry and hence you service need to updated information as required.

Delete an instance

  • DELETE method:
    http://localhost:8761/eureka/apps/{your app id}/{your instance id}
    
    // Example
    http://localhost:8761/eureka/apps/myapp/myhost
    
  • Response: 200 OK

Send an application instance heartbeat

  • PUT method:
    http://localhost:8761/eureka/apps/{your app id}/{your instance id}
    
    // Example
    http://localhost:8761/eureka/apps/myapp/myhost
    
  • Response: 200 OK, or 404 if the instance doesn’t exist

Take an instance out of service

  • PUT method:
    http://localhost:8761/eureka/apps/{your app id}/{your instance id}/status?value=OUT_OF_SERVICE
    
    // Example
    http://localhost:8761/eureka/apps/myapp/myhost/status?value=OUT_OF_SERVICE
    
  • Response: 200 OK, or 500 on failure

Now in the Eureka dashboard you’ll see the large, red OUT_OF_SERVICE text.

Remove the out of service state

  • DELETE method:
    http://localhost:8761/eureka/apps/{your app id}/{your instance id}/status?value=UP
    
    // Example
    http://localhost:8761/eureka/apps/myapp/myhost/status?value=UP
    
  • Response: 200 OK, or 500 on failure

The status UP is optional, it’s a suggestion for the status after the removal of the OUT OF SERVICE override.

Update meta data

  • PUT method:
    http://localhost:8761/eureka/apps/{your app id}/{your instance id}/metadata?key=val
    
    // Example
    http://localhost:8761/eureka/apps/myapp/myhost/metadata?Version=2
    
  • Response: 200 OK, or 500 on failure

Query for all instance of a vip address

  • GET method:
    http://localhost:8761/eureka/vips/{your vipAddress}
    
    // Example
    http://localhost:8761/eureka/vips/myservice
    
  • Response: 200 OK, or 404 if vipAddress does not exist
    <applications>
        <versions__delta>-1</versions__delta>
        <apps__hashcode>UP_1_</apps__hashcode>
        <application>
            <name>MYAPP</name>
            <instance>
                <hostName>myhost</hostName>
                <app>MYAPP</app>
                <ipAddr>10.0.0.10</ipAddr>
                <status>UP</status>
                <overriddenstatus>UNKNOWN</overriddenstatus>
                <port enabled="true">8080</port>
                <securePort enabled="true">8443</securePort>
                <countryId>1</countryId>
                <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
                    <name>MyOwn</name>
                </dataCenterInfo>
                <leaseInfo>
                    <renewalIntervalInSecs>30</renewalIntervalInSecs>
                    <durationInSecs>90</durationInSecs>
                    <registrationTimestamp>1611519061947</registrationTimestamp>
                    <lastRenewalTimestamp>1611519061947</lastRenewalTimestamp>
                    <evictionTimestamp>0</evictionTimestamp>
                    <serviceUpTimestamp>1611518524498</serviceUpTimestamp>
                </leaseInfo>
                <metadata>
                    <Version>2</Version>
                </metadata>
                <homePageUrl>http://myservice:8080</homePageUrl>
                <statusPageUrl>http://myservice:8080/status</statusPageUrl>
                <healthCheckUrl>http://myservice:8080/healthcheck</healthCheckUrl>
                <vipAddress>myservice</vipAddress>
                <secureVipAddress>myservice</secureVipAddress>
                <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
                <lastUpdatedTimestamp>1611519061947</lastUpdatedTimestamp>
                <lastDirtyTimestamp>1611518524498</lastDirtyTimestamp>
                <actionType>ADDED</actionType>
            </instance>
        </application>
    </applications>
    

Query for all instance of a secure vip address

  • GET method:
    http://localhost:8761/eureka/svips/{your svipAddress}
    
    // Example
    http://localhost:8761/eureka/svips/myservice
    
  • Response: 200 OK, or 404 is svipAddress does not exist
    <applications>
        <versions__delta>-1</versions__delta>
        <apps__hashcode>UP_1_</apps__hashcode>
        <application>
            <name>MYAPP</name>
            <instance>
                <hostName>myhost</hostName>
                <app>MYAPP</app>
                <ipAddr>10.0.0.10</ipAddr>
                <status>UP</status>
                <overriddenstatus>UNKNOWN</overriddenstatus>
                <port enabled="true">8080</port>
                <securePort enabled="true">8443</securePort>
                <countryId>1</countryId>
                <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
                    <name>MyOwn</name>
                </dataCenterInfo>
                <leaseInfo>
                    <renewalIntervalInSecs>30</renewalIntervalInSecs>
                    <durationInSecs>90</durationInSecs>
                    <registrationTimestamp>1611519061947</registrationTimestamp>
                    <lastRenewalTimestamp>1611519061947</lastRenewalTimestamp>
                    <evictionTimestamp>0</evictionTimestamp>
                    <serviceUpTimestamp>1611518524498</serviceUpTimestamp>
                </leaseInfo>
                <metadata>
                    <Version>2</Version>
                </metadata>
                <homePageUrl>http://myservice:8080</homePageUrl>
                <statusPageUrl>http://myservice:8080/status</statusPageUrl>
                <healthCheckUrl>http://myservice:8080/healthcheck</healthCheckUrl>
                <vipAddress>myservice</vipAddress>
                <secureVipAddress>myservice</secureVipAddress>
                <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
                <lastUpdatedTimestamp>1611519061947</lastUpdatedTimestamp>
                <lastDirtyTimestamp>1611518524498</lastDirtyTimestamp>
                <actionType>ADDED</actionType>
            </instance>
        </application>
    </applications>
    

Eureka Discovery with Steeltoe outside of ASP.NET

Whilst the previous post showed how we use Steeltoe in an ASP.NET application, sometimes we might not want to (or be able) to use all the nice methods for automatic service discovery etc.

In this post we’re going to create a simple .NET core console application and interact with the discovery service at a slightly lower level.

So…

  • Create yourself a .NET core console application
  • Add the NuGet package Steeltoe.Discovery.Eureka

We need to configure the discovery client. You can use your own configuration method but for this example we’ll hard code parameters into a EurekaClientConfig object.

var config = new EurekaClientConfig
{
   EurekaServerServiceUrls = "http://localhost:8761/eureka/",
   ShouldFetchRegistry = true,
   ShouldRegisterWithEureka = false
};

We’re just writing a client, hence we don’t want to register the application with Eureka, but we do want to fetch registry data.

Next we will create the discovery client, access the applications and get our weatherapi application…

var discoveryClient = new DiscoveryClient(config);
var applications = discoveryClient.Applications;
var service = applications?.GetRegisteredApplication("weatherapi");

Finally we want to find instances of the services associated with the application which are UP and get the URL associated with one of the instances.

var instance = service?.Instances.FirstOrDefault(info => info.Status == InstanceStatus.UP);
if(instance != null)                    
{
   var client = new HttpClient
   {
      BaseAddress = new Uri(instance.HomePageUrl)
   };
   var response = await client.GetAsync("weatherforecast");
   Console.WriteLine(await response.Content.ReadAsStringAsync());
}

If an instance is found we just simply use HttpClient to invoke a GET method on the service and output the response to the console, just to prove everything worked.

This code doesn’t use any load balancing strategies, I’ll leave that to the reader to look into as in a real world scenario we wouldn’t want all clients to use the first instance only.

Creating an ASP.NET client using Eureka and Steeltoe

In the previous post Eureka Server (using Steeltoe) revisited I went through the process of running a Eureka instance and creating a default template based ASP.NET Web API which registers itself with the Eureka server as the application weatherapi.

Let’s now create a basic ASP.NET MVC project to interact with the Eureka server, get an instance of the API service and use it.

Note: This post is almost 100% based on the Channel 9 video Service Discovery with Steeltoe, so credit should go to Tim Hess for the sample code.

  • Create an ASP.NET Core application and then select Web Application (Model View Controller)
  • Add the following NuGet packages, Steeltoe.Discovery.ClientCore, Steeltoe.Discovery.Eureka and System.Net.Http.Json
  • Update the appsettings.json with the following
      "eureka": {
        "client": {
          "serviceUrl": "http://locahost:8761/eureka/",
          "shouldFetchRegistry": "true",
          "shouldRegisterWithEureka": false,
          "validateCertificates": false
        }
      }
    

    Notice we set shouldFetchRegistry to true as we want to get the latest registry information, and we set shouldRegisterWidthEureka to false as, in this case, we don’t want to register this client. Ofcourse change this is your client also exposes services.

  • Within Startup.cs, ConfigureServices add the following
    services.AddDiscoveryClient(Configuration);
    services.AddHttpClient("weather", 
       client => client.BaseAddress = new Uri("http://weatherapi/"))
          .AddServiceDiscovery();
    

    The interesting thing here is that we associate the name “weather” with an address which looks like a URL but really is the name of the service within Eureka that we want to access. Then, by using the AddServiceDiscovery this will be converted to an instance URL representing the instance of the service associated with the app name.

    Note: we can also use load balancing strategies, such as round robin or random.

  • Finally within the Startup.cs, method Configure, add the following
    app.UseDiscoveryClient();
    
  • We’re going to simply copy the WeatherForecast class from the service and add to the client, here it is

    public class WeatherForecast
    {
       public DateTime Date { get; set; }
       public int TemperatureC { get; set; }
       public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
       public string Summary { get; set; }
    }
    
  • Within HomeController.cs we need to add a variable of type IHttpClientFactory which will be injected into the constructor and then uses to call defined HTTP client, this will then used service discovery to return the URL of an instance (as already discussed) and then we’re use that to call the weather API to get a list of values. Here’s the changes required to HomeController.cs
    private readonly IHttpClientFactory _httpClientFactory;
    
    public HomeController(IHttpClientFactory httpClientFactory, ILogger<HomeController> logger)
    {
       _httpClientFactory = httpClientFactory;
       _logger = logger;
    }
    
    public async Task<IActionResult> Index()
    {
       var client = _httpClientFactory.CreateClient("weather");
       var weather = await client.GetFromJsonAsync<IList<WeatherForecast>>("weatherforecast");
       return View(weather);
    }
    
  • Finally, let’s change the Index.cshtml to display the weather forecast data returned in the Index method. Firstly we declare the @model and then simply create a table to output the items from that model in columns and rows, so here’s the change to be added to the top of the file
    @model IList<EurekaWebClient.Controllers.WeatherForecast>
    

    and within the main div, just add the following

    <table class="table">
       <tr><th>Day of Week</th><th>Summary</th><th>Temp</th></tr>
       @foreach (var weather in Model)
       {
          <tr><td>@weather.Date.DayOfWeek</td><td>@weather.Summary</td><td>@weather.TemperatureF</td></tr>
       }
    </table>
    

That should be it. Ensure Eureka is running, your service is up and you should now see the weather service data in a nice little table.

Eureka Server (using Steeltoe) revisited

The Eureka server is used as a registry for maintaining lists of services and their endpoints. It’s used a lot in the microservice world by way of a microservice registering it’s existence somewhere (in this case in Eureka). When a client (whether it’s another service or anything else for that matter) want to access a service it asks the registry for an instance, in this case we connect to the Eureka server and find the services instance(s) that are available for a specific application name and are ofcourse UP.

Installing and running Eureka Server

In a previous post Spring boot Eureka server we wrote a Java application to run a Eureka server. In this post we’re going to use Docker to host the server and then use Steeltoe with .NET to interact with the instance.

To get an image of Eureka server, let’s use the Steeltoe docker image (Spring and others exist, the Steeltoe image is not intended for production, but is fine for what we want to do)

docker pull steeltoeoss/eureka-server

Now run up the docker image using

docker run --publish 8761:8761 steeltoeoss/eureka-server

If all goes well, connect to the Spring Eureka dashboard using

http://locahost:8761

change localhost to the ip/host you’re running the Eureka server from, now you should now see the Spring Eureka web page.

Registering a .NET core client with Eureka

I’ve a post on this topic A .NET service registered with Eureka, but let’s go through the process again with the current version of Steeltoe (as the NuGet packages have changed somewhat).

  • Create an ASP.NET Core Web Application, as this will represent a REST service that we want to interact with. My project is called RegisterExample and is an API project.
  • Add the NuGet package Steeltoe.Discovery.ClientCore and Steeltoe.Discovery.Eureka
  • In Startup.cs within ConfigureServices add the following
    services.AddDiscoveryClient(Configuration);
    
  • Within the Configure method add the following
    app.UseDiscoveryClient();
    
  • Finally add the following to the appsettings.json file
    // Eureka info
      "eureka": {
        "client": {
          "serviceUrl": "http://localhost:8761/eureka/",
          "shouldFetchRegistry": "false",
          "shouldRegisterWithEureka": true,
          "validateCertificates": false
        },
        "instance": {
          "appName": "weatherapi",
          "port": "8080",
          "ipAddress": "localhost",
          "preferIpAddress": true
        }
      }
    

We’re going to leave the API with the default weatherapi, hence the appName in the appsettings.

Notice we set the value for “shouldFetchRegistry” to false as this service will not be acting as a client to any other services. Obviously change this is you also need to discovery other services. “shouldRegisterWithEureka” is set to true as we want this service to automatically register itself with Eureka.

Now navigate to the URL of your Eureka server again (or refresh) and you should see a new Application. In my case I have an application with the name weatherapi. This name comes from our appsettings.json configuration application/name.

Info and Health

If you click on the instance link within the Eureka server dashboard, you will navigate to

https://localhost:5001/info

(or whatever ip/hostname and port your service is running on) you get a 404, so let’s fix that in our project.

Info, by default will display some basic information about the application, product version etc. However you can also add further custom information if you want, but example the git build SHA1 hash or just some general info.

  • Add NuGet package Steeltoe.Management.EndpointCore
  • In Startup.cs ConfigureServices, add the following
    services.AddSingleton<IInfoContributor, MyInfoContributor>();
    services.AddInfoActuator(Configuration);
    services.AddHealthActuator(Configuration);
    

    The first of these lines will add our implementation of an IInfoContributor to allow for custom info.

  • Still in Startup.cs, but now the method Configure, add the following to the UseEndpoints endpoint routes

    endpoints.Map<InfoEndpoint>();
    endpoints.Map<HealthEndpoint>();
    
  • Now we’ll create a simple implementation of and IInfoContributor which allows us to add our own info, so add the following class

    public class MyInfoContributor : IInfoContributor
    {
       public void Contribute(IInfoBuilder builder)
       {
          builder.WithInfo("MyInfo", new { SomeName = "Scooby Doo" });
       }
    }
    

Now when we run our service we hope to see our info data, however by default Steeltoe seems to set the info and health endpoint to /actuator/info and /actuator/health respectively. Eureka seems to expect /info. So go to the appsettings.json and add the following to the Instance section

"StatusPageUrlPath": "/actuator/info",
"HealthCheckUrlPath": "/actuator/health" 

Note: I’m not sure what I’m missing here and why the defaults don’t match up, but these configuration changes will tell the Eureka server (when we register our service with it) that it should use these paths for info and health.

Now, if you run the service again for /actuator/info you should see something like this

{"MyInfo":{"someName":"Scooby Doo"},
"applicationVersionInfo":{"ProductName":"RegisterExample",
"FileVersion":"1.0.0.0","ProductVersion":"1.0.0"},
"steeltoeVersionInfo":"ProductName":"Steeltoe.Management.Endpoint",
"FileVersion":"3.0.2.0","ProductVersion":
"3.0.2\u002B4089779c66d127f40325a3be9b613149b3b090f2"}}

and health something like

{"status":"UP","details":{"liveness":{},"diskSpace":{"total":4000769372160,
"free":3889734168576,"threshold":10485760,"status":"UP"},"eurekaServer":
{"remoteInstStatus":"UNKNOWN","fetchStatus":"Not fetching","heartbeat":
"Successful","heartbeatStatus":"UP","heartbeatTime":"2021-01-23T20:40:44",
"status":"UP","applications":"NONE"},"readiness":{}}}