Creating MicroService with .Net 5
Tags: dotnet, microservices, csharp, beginners
Published: 2021-04-01 06:23:25

In this Article we will be create a MicroService with .Net 5

You can watch the full video on Youtube

{% youtube 1gPz0G6GZCU %}

You can get the source code from GitHub:
https://github.com/mohamadlawand087/v23-MicroService

So what we will cover today: 

- Ingredients
- How to create .NET 5 MicroService
- Interact with your MicroService endpoints
- Manage configuration and secrets
- Handle errors
- Add healthChecks to our service
- Adjust logs for development and production

As always you will find the source code in the description down below. Please like, share and subscribe if you like the video. It will really help the channel

Ingredients

- VS Code ([https://code.visualstudio.com/](https://code.visualstudio.com/))
- Dotnet 5 SDK ([https://dotnet.microsoft.com/download](https://dotnet.microsoft.com/download))

Creating our MicroService application

```bash
dotnet new webapi -o SampleService --no-https
```

The reason we disabled https is to make it simple and usually in a MicroService architecture handling SSL is done on infrastructure level.

Let us prepare our API , we will use [https://www.worldcoinindex.com/apiservice](https://www.worldcoinindex.com/apiservice) to get all of the latest crypto currency prices

Now let us start developing, we will first create a class that will allow us to read the configuration which is in our case the API Key, since we don't want to store this information inside our source code.

In the root directory we will create a new folder called Configurations and inside that folder will create a class called ServiceSettings

```csharp
public class ServiceSettings
{
    // This will represent the Url
    public string CoinsPriceUrl { get; set; }

    // This will represent the api key
    public string ApiKey { get; set; }
}
```

Now let us update our appsettings.json which represent out configuration store for the url.

```json
"ServiceSettings": {
    "CoinsPriceUrl" : "www.worldcoinindex.com/apiservice"
  },
```

We should never add the secrets into our appsettings for this reason we will be using .net secret manager. To initiate it we will use the following

```bash
dotnet user-secrets init
dotnet user-secrets set ServiceSettings:ApiKey 6zvcCSO2DhUdUNMaIEEk4KTjCNfDuR8ZggM
```

Now we need to register our configuration inside our startup class so the application will be able to know about the host url as well the secrets keys that we have.

Inside the startup class we need to add the following

```csharp
// This will allow us read the configurations from the appsettings as well the 
// secret key store
services.Configure<ServiceSettings>(Configuration.GetSection(nameof(ServiceSettings)));
```

The next step is to create an API client which will allow us to connect with our API.

We will be utilising RestSharp API client to do all of our calls, so we need to install our nuget package into our application

```bash
dotnet add package RestSharp
```

Inside our root folder will create a folder called Services and inside the Services folder will create the ApiClient class and its interface

```csharp
public interface IApiClient
{
    CoinsInfo ConnectToApi(string currency);
}
```

```csharp
public class ApiClient : IApiClient
{
    private readonly ServiceSettings _settings;

    public ApiClient(IOptions<ServiceSettings> settings)
    {
        _settings = settings.Value;
    }

    public CoinsInfo ConnectToApi(string currency)
    {
        var client = new RestClient($"https://{_settings.CoinsPriceUrl}/ticker");
        var request = new RestRequest(Method.GET);
        request.RequestFormat = DataFormat.Json;
        
        request.AddParameter("key", _settings.ApiKey, ParameterType.GetOrPost);
        request.AddParameter("label", "ethbtc-ltcbtc-BTCBTC-eosbtc", ParameterType.GetOrPost);
        request.AddParameter("fiat", currency, ParameterType.GetOrPost);

        var response = client.Get(request);

        var markets =  JsonSerializer.Deserialize<CoinsInfo>(response.Content);

        return markets;
    }

    // Utilising the .Net 5 records features
    public record Market (string Label, string Name, double Price);
    public record CoinsInfo (Market[] Markets);
}
```

Now we need to update our startup class with the new service so we can use it. Inside our startup class let us add the following

```csharp
services.AddScoped<IApiClient, ApiClient>();
```

Now its time to create our controller, inside our controller folder will create a new class called CoinsMarketController

```csharp
[ApiController]
[Route("[controller]")]
public class CoinsMarketController : ControllerBase
{
    private readonly ILogger<CoinsMarketController> _logger;
    private readonly IApiClient _apiClient;

    public CoinsMarketController(
        ILogger<CoinsMarketController> logger,
        IApiClient apiClient)
    {
        _logger = logger;
        _apiClient = apiClient;
    }

    [HttpGet]
    [Route("{currency}")]
    public IActionResult Get(string currency)
    {
        var result = _apiClient.ConnectToApi(currency);
    
        return Ok(result);
    }
}
```

Now one of the main features of MicroServices is that they are reliable, And with this external dependency there might be a chance for this service to go down due to the 3rd party service.

One of the features we can add to our service to make more reliable is retrys so in case the call has failed we automatically retry the request. 

We can utilise the Polly extension from Microsoft, to do that we need to add a package

```bash
dotnet add package Microsoft.Extensions.Http.Polly
```

Polly will allow us to do transient error policies which will allow us to define how do we want the error to be handled.

To implement these policies we need to go to ApiClient class and update it to the following.

```csharp
public class ApiClient : IApiClient
{
    private readonly ServiceSettings _settings;
    private readonly ILogger<ApiClient> _logger;
    private Policy<IRestResponse> retryPolicy;

    private static readonly List<HttpStatusCode> invalidStatusCodes = new List<HttpStatusCode> {
        HttpStatusCode.BadGateway,
        HttpStatusCode.Unauthorized,
        HttpStatusCode.InternalServerError,
        HttpStatusCode.RequestTimeout,
        HttpStatusCode.BadRequest,
        HttpStatusCode.Forbidden,
        HttpStatusCode.GatewayTimeout
    };
    
    public ApiClient(IOptions<ServiceSettings> settings, ILogger<ApiClient> logger)
    {
        _logger = logger;
        _settings = settings.Value;
    }

    public CoinsInfo ConnectToApi(string currency)
    {
				// We will use the Exponential backoff
        retryPolicy = Policy                
            .HandleResult<IRestResponse>(resp => invalidStatusCodes.Contains(resp.StatusCode))                
            .WaitAndRetry(10, i => TimeSpan.FromSeconds(Math.Pow(2, i)), (result, timeSpan, currentRetryCount, context) =>
            {
                _logger.LogError($"Request failed with {result.Result.StatusCode}. Waiting {timeSpan} before next retry. Retry attempt {currentRetryCount}");
            });

        var client = new RestClient($"https://{_settings.CoinsPriceUrl}/ticker");
        var request = new RestRequest(Method.GET);
        request.RequestFormat = DataFormat.Json;
        
        request.AddParameter("key", _settings.ApiKey, ParameterType.GetOrPost);
        request.AddParameter("label", "ethbtc-ltcbtc-BTCBTC-eosbtc", ParameterType.GetOrPost);
        request.AddParameter("fiat", currency, ParameterType.GetOrPost);

        var policyResponse = retryPolicy.ExecuteAndCapture(() =>
        {
            var response = client.Get(request);
            return response;
        });
        if(policyResponse.Result != null)
        {
            var markets =  JsonSerializer.Deserialize<CoinsInfo>(policyResponse.Result.Content);

            return markets;
        } else
        {
            return null;
        }
        // var response = client.Get(request);

        // var markets =  JsonSerializer.Deserialize<CoinsInfo>(response.Content);

        // return markets;
    }

    // Utilising the .Net 5 records features
    public record Market (string Label, string Name, double Price);
    public record CoinsInfo (Market[] Markets);
}
```

So this is a very good way and a standard way to keep our MicroService Reliable, but this is solution is executing after the request is being processed, and the best way to check for a potential downtime is to do that before the service execute. And to do that we will need to utilise health check. 

So how do we utilise health check, we need to do the following. First we need to add the HealthCheck Service inside our startup class ConfigureServices method 

```csharp
services.AddHealthChecks();
```

Then we need to update our middlewear to utilise the HealthCheck and create an endpoint for it, which basically adds a route to check the health

```csharp
endpoints.MapHealthChecks("/health");
```

Now we need to add the HealthCheck for the external endpoint as the basic HealthCheck will only check the status of our app without triggering 3rd party services. 

Our external endpoint HealthCheck will do a ping on the api to check we get a response or not.

We start by adding a new folder called HealthChecks and inside that folder we add a new class called CoinsInfoHealthCheck and will add the following code. 

```csharp
// We need to implement the IHealthCheck interface and 
// implement the method
public class CoinsInfoHealthCheck : IHealthCheck
{
    private readonly ServiceSettings _settings;

    public CoinsInfoHealthCheck(IOptions<ServiceSettings> settings)
    {
        _settings = settings.Value;
    }

    public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        // This is default implementation
        Ping ping = new();
        var reply = await ping.SendPingAsync(_settings.CoinsPriceUrl);

        if(reply.Status != IPStatus.Success)
            return HealthCheckResult.Unhealthy();
        
        return HealthCheckResult.Healthy();
    }
}
```

Now we need to register this HealtCheck in our startup class.

now we need to configure logs to output json only to make easy to send the logs to our host log manager in an MicroServices Architecture. Inside our Program.Cs we need to add the following to CreateHostBuilder

```csharp
.ConfigureLogging((context, logging) => {
                    if(context.HostingEnvironment.IsProduction())
                    {
                        logging.ClearProviders();
                        logging.AddJsonConsole();
                    }
                })
```

Thank you for reading.