Connect to Salesforce Data Cloud Ingestion API using C# and HttpClient

As a C# developer, to properly connect to Salesforce Data Cloud, you need to do 3 steps:

  1. Request an Access Token using the credentials provided for you.
  2. Exchange the Access Token for a Data Cloud Token
  3. POST data to the Ingestion API using the Data Cloud Token

So in other words, you do authorization twice before you can POST your JSON to an Ingestion endpoint. So let’s get started.

BEFORE WE BEGIN: THE PREREQUISITES

As a responsible developer, we use the HttpClientFactory to create a HttpClient. So in the Program.cs, we have implemented an IHttpClientFactory instance:

services.AddHttpClient("httpClient");

STEP 1: REQUEST AN ACCESS TOKEN USING BASIC AUTHENTICATION

The AccessToken is in the format of an OAuth token, so we create a class for that:

using System.Text.Json.Serialization;

namespace AzureFunctionApp.Salesforce
{
  public class OAuthToken
  {
        [JsonPropertyName("access_token")]
        public string AccessToken { get; set; }

        [JsonPropertyName("signature")]
        public string Signature { get; set; }

        [JsonPropertyName("scope")]
        public string Scope { get; set; }

        [JsonPropertyName("instance_url")]
        public string InstanceUrl { get; set; }

        [JsonPropertyName("id")]
        public string Id { get; set; }

        [JsonPropertyName("token_type")]
        public string TokenType { get; set; }

        [JsonPropertyName("issued_at")]
        public string IssuedAt { get; set; }
    } 
}

Then we can create a function that gets an OAuthToken from Salesforce:

using System.Net.Http.Json;
using System.Text;

namespace MyCode
{
  public class SalesforceRepository
  {
    private IHttpClientFactory _httpClientFactory;

    public SalesforceAccessRepository(IHttpClientFactory httpClientFactory)
    {
      _httpClientFactory = httpClientFactory;
    }

    public async Task<OAuthToken?> GetOAuthTokenAsync()
    {
      string? username = "xxxxx";
      string? password = "xxxxx";
      string? oAuthUrl = "https://xxxxx.my.salesforce.com/services/oauth2/token"

      var client = _httpClientFactory.CreateClient("HttpClient");
      var authToken = Encoding.ASCII.GetBytes($"{username}:{password}");
      client.DefaultRequestHeaders.Authorization = 
	      new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(authToken));
      var content = new MultipartFormDataContent { { new StringContent("client_credentials"), "grant_type" } };

      var response = await client.PostAsync(oAuthUrl, content);
      if (!response.IsSuccessStatusCode)
        throw new Exception($"{response.StatusCode}: {response.ReasonPhrase}");

      var responseJson = await response.Content.ReadFromJsonAsync<OAuthToken>();
      return responseJson;
    }
  }
}

STEP 2: EXCHANGE THE ACCESS TOKEN FOR A DATA CLOUD TOKEN

The Data Cloud token is in the format of a Json Web Token (JWT), so we need a class for that:

using System.Text.Json.Serialization;

namespace MyCode
{
  public class JsonWebToken
  {
    [JsonPropertyName("access_token")]
    public string AccessToken {  get; set; } 

    [JsonPropertyName("instance_url")]
    public string InstanceUrl {  get; set; } 

    [JsonPropertyName("token_type")]
    public string TokenType { get; set; }  

    [JsonPropertyName("issued_token_type")]
    public string IssuedTokenType { get; set; }  

    [JsonPropertyName("expires_in")]
    public int ExpiresIn { get; set; } 

  }
}

Then we can create another method in the SalesforceRepository from STEP1:

    public async Task<JsonWebToken?> GetJsonWebTokenAsync(string bearerToken)
    {
      string? url = "https://xxxxx.my.salesforce.com/services/a360/token";

      var client = _httpClientFactory.CreateClient("HttpClient");
      var data = new[]
      {
        new KeyValuePair<string, string>("grant_type", "urn:salesforce:grant-type:external:cdp"),
        new KeyValuePair<string, string>("subject_token", bearerToken),
        new KeyValuePair<string, string>("subject_token_type", "urn:ietf:params:oauth:token-type:access_token")
      };
      var content = new FormUrlEncodedContent(data);

      var response = await client.PostAsync(url, content);
      if (!response.IsSuccessStatusCode)
        throw new Exception($"{response.StatusCode}: {response.ReasonPhrase}");

      var responseJson = await response.Content.ReadFromJsonAsync<JsonWebToken>();
      return responseJson;
    }

STEP 3: POST DATA TO THE INGESTION API

With the 2 authorization function, we are now read to POST some data to an ingestion endpoint:

    public async Task<string> Ingest(string jsonData)
    {
      OAuthToken? oauthToken = await GetOAuthTokenAsync();
      JsonWebToken? jwt = await GetJsonWebTokenAsync(oauthToken.AccessToken);
      string? accessToken = jwt?.AccessToken;  

      var client = _httpClientFactory.CreateClient("HttpClient");
      client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);

      using (var content = new StringContent(jsonData, Encoding.UTF8, "application/json"))
      {
        var response = await client.PostAsync("https://xxxxx.salesforce.com/api/v1/ingest/sources/xxxxx", content);
        if (!response.IsSuccessStatusCode)
          throw new Exception($"{response.StatusCode}: {response.ReasonPhrase}");

        var responseJson = await response.Content.ReadAsStringAsync();
        return responseJson;
      }
    }

Please note that the Salesforce Ingestion API returns 202 Accepted, not 200 OK. 202 Accepted means that Salesforce have received the data and will look at them at some point. It does not mean that the JSON you posted is in the correct format nor that the data write is successful.

That’s it. You are now a Salesforce expert. Happy coding.

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET | Tagged , , , , , , , , , , , , , , , , | Leave a comment

C# POST x-www-form-urlencoded using HttpClient

To POST x-www-form-urlencoded data in C# using the HttpClient, you can do the following:

using System.Net.Http;
 
private static HttpClient _httpClient = new HttpClient();

public async Task<string> PostFormUrlEncodedData()
{
    var data = new[]
    {
        new KeyValuePair<string, string>("formfield1", "formvalue1"),
        new KeyValuePair<string, string>("formfield2", "formvalue2"),
        new KeyValuePair<string, string>("formfield3", "formvalue3")
    };
    var content = new FormUrlEncodedContent(data);

    var response = await client.PostAsync(url, content);
    if (!response.IsSuccessStatusCode)
        throw new Exception($"{response.StatusCode}: {response.ReasonPhrase}");

    var responseJson = await response.Content.ReadAsStringAsync();
      return responseJson;	
}

The FormUrlEncodedContent will automatically set the content-type to application/x-www-form-urlencoded.

Before using the HttpClient, consider using a HttpClientFactory, as the HttpClient is a shared object and creating a HttpClient for each call will lead to socket exhaustion.

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET | Tagged , , , , , , , , | 1 Comment

Convert JSON Arrays into batches of JSON Arrays using the LINQ Chunk method

This is one way of converting a JSON array of any JSON objects into batches. This function uses the LINQ Chunk method to convert a 1 dimensional array input into a 2 dimensional array output:

Example input array:

[
    { "a": "1" },
    { "a": "2" },
    { "a": "3" },
    { "a": "4" },
    { "a": "5" },
    { "a": "6" },
    { "a": "7" }
]

Converted into batches of 3:

[
    [
        {"a":"1"},
        {"a":"2"},
        {"a":"3"}
    ],
    [
        {"a":"4"},
        {"a":"5"},
        {"a":"6"}
    ],
    [
        {"a":"7"}
    ]
]

THE CODE:

using System;
using System.Collections.Generic;
using System.Linq;

// For demo purpose: Grab a Json array and convert it into an array of objects
string jsonInputString = "[ { \"a\": \"1\" }, { \"a\": \"2\" }, { \"a\": \"3\" }, { \"a\": \"4\" }, { \"a\": \"5\" }, { \"a\": \"6\" }, { \"a\": \"7\" } ]";
List<object> jsonInput = System.Text.Json.JsonSerializer.Deserialize<List<object>>(jsonInputString);

// Create a new list of objects, and use the Chunk method to create batches
// of a certain size
List<object> batch = new List<object>();
foreach (var chunk in jsonInput.Chunk(3))
{
  batch.Add(chunk);
}

// For demo purposes, convert the batched objects as a Json string
string jsonOutputString = System.Text.Json.JsonSerializer.Serialize(batch);

// Output the contents
Console.WriteLine(jsonOutputString);

When using the Chunk method, C# does the heavy lifting of breaking the array into batches of a maximum size.

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET | Tagged , , , , , , , | Leave a comment

Azure Functions: No job functions found. Try making your job classes and methods public.

This error message might occur when working with Azure Functions in Isolated Mode:

No job functions found. Try making your job classes and methods public. If you’re using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you’ve called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).

It occurred to me when upgrading the NuGet package references for Microsoft.Azure.Functions.Worker:

<ItemGroup>
  <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
  <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.4" OutputItemType="Analyzer" />
  <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.23.0" />
</ItemGroup>

To resolve this, do the following:

STEP 1: ADD A FunctionsEnableWorkerIndexing PROPERTY TO THE .csproj FILE:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
    <FunctionsEnableWorkerIndexing>false</FunctionsEnableWorkerIndexing>    
    <OutputType>Exe</OutputType>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>    
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.4" OutputItemType="Analyzer" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.23.0" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>
  <ItemGroup>
    <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
  </ItemGroup>
</Project>

STEP 2: CLEAN YOUR SOLUTION

In Visual Studio, right click the solution, Click in “clean solution” button.

That’s it. That should resolve the issue.

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET, Microsoft Azure | Tagged , , , | Leave a comment

C# Lowercase all JSON attributes in your API responses

When creating an API in C#, you can control how your JSON response should be formatted. The classic way is to us the JsonPropertyName attribute and define the output in a per-field manner:

using System.Text.Json.Serialization;

namespace MyCode
{
    public class Response
    {
        [JsonPropertyName("id")]
        public int ID {get; set;}

        [JsonPropertyName("customerId")]
        public long CustomerID { get; set; }
    }
}

But you can also define a JsonNamingPolicy global rule that will work for all of your fields at once.

Please note that the JsonPropertyName attribute will overrule the JsonNamingPolicy, so remove all attributes from your response model classes first.

STEP 1: CREATE A JsonNamingPolicy

using System.Text.Json;
using System.Text;

namespace MyCode
{
  public class LowercaseNamingPolicy : JsonNamingPolicy
  {
    public override string ConvertName(string name)
    {
      return name.ToLower();
    }
  }
}

The JsonNamingPolicy contains one method, ConvertName. In my case, the code is simple, as I’m just lowercasing the property.

STEP 2: ADD THE NAMING POLICY AS A JSON OPTION TO THE AddControllers() METHOD

builder.Services.AddControllers()
  .AddJsonOptions(options =>
  {
    options.JsonSerializerOptions.PropertyNamingPolicy = new LowercaseNamingPolicy();
  }
);

Add .AddJsonOptions() to the AddControllers() method, and the JsonNamingPolicy will be called for each field in each response.

WHAT IF I WOULD LIKE TO USE camelCase INSTEAD?

The JsonNamingPolicy already have a built in version of camelCase, so no need for you to code that one from scratch.

builder.Services.AddControllers().AddJsonOptions(options =>
  {
    options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
  }
);

In fact, when using .NET 8, there is built in support for CamelCase (camelCase), KebabCase lower and KebabCase upper (kebab-case and KEBAB-CASE) and SnakeCase lower/Snakecase upper (snake_case and SNAKE_CASE).

That’s it. Happy coding.

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET | Tagged , , , , , , , , | Leave a comment

C# Sort JArray NewtonSoft.Json

OK, just to burst your bubble: You cannot sort JArray. But if your JArray is just a list of numbers, or letters, you can convert the JArray into a list, sort that, and put it back into your JArray.

In my code I’m simulating this JSON array of strings:

["c", "e", "a", "d", "b"]

And I would like it to be sorted alphabetically. This is the code:

// Setting up my test data
JArray jArray = new JArray();
jArray.Add("c");
jArray.Add("e");
jArray.Add("a");
jArray.Add("d");
jArray.Add("b");

// This is the actual sorting
List<string> alphabet = jArray.ToObject<List<string>>();
alphabet.Sort();
jArray = JArray.FromObject(alphabet);

// Output the result
foreach (var item in jArray) 
{
  Console.WriteLine(item);
}

The JArray is first converted into a strongly typed list of strings. Then I can sort this list. Finally the sorted list can be put back into the JArray.

The output is:

a
b
c
d
e

That’s it. Happy coding.

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET | Tagged , , , , | 1 Comment

C# Sort List of Dynamic Objects

The dynamic type in C# is a static type that bypasses static type checking. This makes it possible for you to work with objects like they were strongly typed, but without having to create named model classes beforehand.

Imagine that you have some dynamic data and you wish to sort it. In this example, I have a JSON array of users that I wish to sort after age:

[
    {
        "name": "Arthur Dent",
        "age": 42
    },
    {
        "name": "Ford Prefect",
        "age": 1088
    },
    {
        "name": "Zaphod Beeblebrox",
        "age": 17
    }
]

To sort this, I first convert the data from a JSON string to a List<dynamic>(), then I sort it. I use Newtonsoft.Json as converter:

List<dynamic> input = JsonConvert.DeserializeObject<List<dynamic>>(json);

input = input.OrderBy(a => a.age).ToList();

foreach (var inputItem in input) 
{
  Console.WriteLine(inputItem);
}

The output of the method is:

{
  "name": "Zaphod Beeblebrox",
  "age": 17
}
{
  "name": "Arthur Dent",
  "age": 42
}
{
  "name": "Ford Prefect",
  "age": 1088
}

That’s it. Happy coding.

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET | Tagged , , , , , | Leave a comment

C# Add UserAgent to Application Insights Request Telemetry using a Middleware

This is probable rather specific, but I have this API endpoint, and in my Application Insights I need the UserAgent whenever I log a Request.

Custom Properties of a Request Telemetry in Application Insights
Custom Properties of a Request Telemetry in Application Insights

To do so I need to implement a Middleware. Middleware’s are pieces of code that can run for each request and response in your code.

I will write a piece of middleware that will collect the UserAgent from the incoming request and add it as a custom property to my RequestTelemetry.

PREREQUISITES:

You need to have Application Insights enabled in your application. If you are unaware of how to do so, read this article C# Log to Application Insights and File from your .NET 6 Application to see how to set it up.

STEP 1: CREATE A MIDDLEWARE

A Middleware needs a constructor and a InvokeAsync method:

using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.AspNetCore.Http;
using System.Net.Http;
using System.Threading.Tasks;

namespace MyCode
{
  public class TelemetryMiddleware
  {
    private readonly RequestDelegate _next;

    public TelemetryMiddleware(RequestDelegate next)
    {
      _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
      context.Request.EnableBuffering();
      AddUserAgentToApplicationInsights(context);
      await _next(context);
    }

    private static void AddUserAgentToApplicationInsights(HttpContext context)
    {
      if (context.Request.Method == HttpMethods.Post)
      {
        var telemetry = context.Features.Get<RequestTelemetry>();
        if (telemetry == null)
          return;
        telemetry.Properties.Add("UserAgent", context.Request.Headers.UserAgent);
      }
    }
  }
}

Please note that in this example, I only add the UserAgent for POST calls.

If the request is a POST call, I retrieve the RequestTelemetry class and adds the UserAgent as a custom property.

STEP 2: ENABLE THE MIDDLEWARE:

Inject the middleware in your IApplicationBuilder:

public static void Main(string[] args)
{
    var builder = CreateHostBuilder(args);
    var startup = new Startup(builder.Configuration, builder.Environment);
    ...    
    WebApplication app = builder.Build();
    ...
    ...
    app.UseMiddleware<TelemetryMiddleware>();
    ...
    ...
    app.Run();
}

RESULT:

For each Request logged in Application Insights:

Application Insights Requests
Application Insights Requests

… I now have the UserAgent in the custom properties:

UserAgent as a Custom Property in the Application Insights Log
UserAgent as a Custom Property in the Application Insights Log

That’s it. You are now an Application Insights Expert. Happy coding.

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET, Microsoft Azure | Tagged , , , , , , | Leave a comment

C# Marking Deprecated Code As Obsolete

If a certain piece of code is no longer relevant or needed, or if it’s no longer maintained, you can mark it as obsolete.

Use the [Obsolete] tag to mark the code. You can mark a function:

[Obsolete("Use GetAsync<T>(string url) instead")]
public async Task<T?> GetAsync<T>(string url, string parameters)
{
  var client = _httpClientFactory.CreateClient("HttpClient");
  var response = await client.GetAsync(url + parameters);
  if (!response.IsSuccessStatusCode)
    throw new Exception($"{response.StatusCode}: {response.ReasonPhrase}");
  var responseJson = await response.Content.ReadFromJsonAsync<T>();;
  return responseJson;
}

Or you can mark a class:

namespace CatFacts
{
  [Obsolete("The code no longer supports weather forecasts")]
  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; }
  }
}

When the code is marked as [Obsolete], you will receive a warning when calling the function or referencing the class:

Calling code marked with [Obsolete] will give a warning.
Calling code marked with [Obsolete] will give a warning.

You can also return an error if the code is called. Include a true Boolean in the attribute:

[Obsolete("Use GetAsync<T>(string url) instead", true)]
public async Task<T?> GetAsync<T>(string url, string parameters)
{
  var client = _httpClientFactory.CreateClient("HttpClient");
  var response = await client.GetAsync(url + parameters);
  if (!response.IsSuccessStatusCode)
    throw new Exception($"{response.StatusCode}: {response.ReasonPhrase}");
  var responseJson = await response.Content.ReadFromJsonAsync<T>();;
  return responseJson;
}

The compiler will now throw an error:

[Obsolete("", true)] will return an error
[Obsolete(“”, true)] will return an error when called

SO WHAT IS THE DIFFERENCE BETWEEN DEPRECIATED, DEPRECATED AND OBSOLETE CODE?

Depreciated and Deprecated are the same thing. Technically, we don’t depreciate code, we deprecate it.

  • Deprecated code is code that currently is supported, but in the future will be removed. It’s the way you ease you out of functionality that you wish to remove. But at the same time give users of your code time to adapt and find a new solution – preferably use a new method or class that you include at the same time the old code is deprecated.
  • Obsolete code is code that is no longer supported, or is irrelevant. Calling obsolete code is at your own risk as the result is deemed undefined. Usually this means that a feature have been removed from your codebase, but the call is still there to allow existing code to compile.

WHAT IF I MESS UP THE 2 TERMS?

Don’t worry, even Microsoft have mixed the terms. Look at the example above. The attribute is called [Obsolete], but the compiler warning say “Deprecated“.

As long as you supply a good description along with the [Obsolete] attribute, everything will be fine.

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET | Tagged , , , , | Leave a comment

C# Game Of Life

The Game Of Life is a cell life simulator devised by the Cambridge mathematician John Conway.

Game Of Life in a Console Application

The simulator serves as an excellent coding example to learn new programming languages. The rules are simple. You have a grid in a predetermined size, each cell can live or die based on a set of rules:

  • If a cell is alive, and there are 0 or 1 living neighbors, the cell dies of loneliness.
  • If a cell is alive, and there are 2 or 3 living neighbors, the cell survives.
  • If a cell is alive, and there are 4 or more living neighbors, the cell dies of overpopulation.
  • If a cell is dead, and there are 3 living neighbors, the cell comes alive.

This is an example of a simple game of life implementation in C# and .net 8. The simulation uses the console output to visualize the game progress.

UPDATE: Code have been updated to recalculate the complete board before progressing. Thanks to Alexander Gats for that one!

STEP 1: DEFINE THE GRID SIZE AND PRE-SEED A NUMBER OF CELLS

const int BOARD_WIDTH = 80;
const int BOARD_LENGTH = 25;
const int ACTIVE_CELLS_AT_START = 400;

bool[,] board = new bool[BOARD_WIDTH, BOARD_LENGTH];
var random = new Random();

// Populate a number of cells to begin with
for (int i = 0; i < ACTIVE_CELLS_AT_START; i++)
{
  board[random.Next(board.GetLength(0)), random.Next(board.GetLength(1))] = true;
}

First we define the game prerequisites; the size of the grid (called board) and how many cells should be alive when the simulation starts.

Then we populate the grid by randomly setting cells in the grid to true (true meaning alive, false meaning dead).

STEP 2: MAKE AN ALGORITHM TO CALCULATE THE NUMBER OF LIVING NEIGHBORS

int CountNeighbors(bool[,] board, int x, int y)
{
  int ac = 0;
  // Double loop from 1 up and 1 left to 1 down and 1 right
  // Gives 9 iterations
  for (int i = x - 1; i <= x + 1; i++)
  {
    for (int j = y - 1; j <= y + 1; j++)
    {
      // Only count cells within the board (IsValidIndex)
      // and do not count the cell itself (IsYourself)
      if (IsValidIndex(i, j, board.GetLength(0), board.GetLength(1)) 
          && !IsYourself(x, y, i, j) 
          && board[i, j])
        ac++;
    }
  }

  return ac;
}

bool IsValidIndex(int i, int j, int rows, int cols)
{
  return i >= 0 && i < rows && j >= 0 && j < cols;
}

bool IsYourself(int x, int y, int i, int j)
{
  return x == i && y == j;
}

The CountNeighBors takes the game grid and the x/y position of the cell to calculate how many of the 8 neighboring cells are alive.

The white cells are the cells to check

The algorithm needs a double loop to check the neighbors.

But it also needs to be smart enough to count even if the cell is on the edge of the grid. The IsValidIndex checks to see if the cell being investigated is within the board.

Also, we should not count the cell being investigated. The IsYourself checks to see if the cell being investigated is the current cell, disregard that cell.

STEP 3: CALCULATE THE CELL LIFE

bool CalculateCellLife(bool[,] board, int x, int y)
{
  int neighbors = CountNeighbors(board, x, y);
  // If cell is alive:
  if (board[x, y] == true)
  {
    // 0 or 1 neighbors: Die of loneliness
    if (neighbors <= 1)
      return false;
    // 4 or more neighbors: Die of overpopulation
    if (neighbors >= 4)
      return false;
    return true;
  }
  // If cell is not alive:
  else
  {
    // 3 neighbors give birth to a new cell
    if (neighbors == 3)
      return true;
    return false;
  }
}

This is the actual game of life algorithm.

The method uses the CountNeighbors function to calculate the number of alive cells surrounding a specified cell, and return true if cell is alive and false if cell is dead.

As stated before, the rules are that a living cell will die if there are 0,1 or more than 4 alive surrounding cells, and come alive if there is precisely 3 living cells surrounding it.

STEP 4: MAKE A FUNCTION TO PROGRESS THE SIMULATION BY ONE STEP

bool[,] StepSimulation(bool[,] currentBoard)
{
  // Recalculate a new board based on the values
  // of the current board
  bool[,] newBoard = new bool[BOARD_WIDTH, BOARD_LENGTH];
  for (int x = 0; x < board.GetLength(0); x++)
  {
    for (int y = 0; y < board.GetLength(1); y++)
    {
      newBoard[x,y] = CalculateCellLife(currentBoard, x, y);
    }
  }
  return newBoard;
}

Here we calculate the next step in the simulation by creating a new board based on the values of the old board.

STEP 5: DRAW A CELL IN THE CONSOLE

void DrawCell(int x, int y, bool isAlive)
{
  Console.SetCursorPosition(x, y);
  if (!isAlive)
    Console.BackgroundColor = ConsoleColor.Black;
  else
    Console.BackgroundColor = ConsoleColor.White;
  Console.Write(" ");
}

To draw a cell in the console I simply move the cursor to the determined position and draws a space with either black (dead) or white (alive) background.

STEP 6: THE MAIN GAME LOOP

while (true)
{
  // Progress the simulation
  board = StepSimulation(board);
  // Draw the new board
  for (int x = 0; x < board.GetLength(0); x++)
  {
    for (int y = 0; y < board.GetLength(1); y++)
    {
      DrawCell(x, y, board[x, y]);
    }
  }
}

This is the main game loop that loops forever, takes each cell on the board, calculates if the cell must live or die, and draws the result to screen.

COMPLETE CODE:

This is the code in its completion:

const int BOARD_WIDTH = 80;
const int BOARD_LENGTH = 25;
const int ACTIVE_CELLS_AT_START = 400;

bool[,] board = new bool[BOARD_WIDTH, BOARD_LENGTH];
var random = new Random();

// Populate a number of cells to begin with
for (int i = 0; i < ACTIVE_CELLS_AT_START; i++)
{
  board[random.Next(board.GetLength(0)), random.Next(board.GetLength(1))] = true;
}

while (true)
{
  // Progress the simulation
  board = StepSimulation(board);
  // Draw the new board
  for (int x = 0; x < board.GetLength(0); x++)
  {
    for (int y = 0; y < board.GetLength(1); y++)
    {
      DrawCell(x, y, board[x, y]);
    }
  }
}

bool[,] StepSimulation(bool[,] currentBoard)
{
  // Recalculate a new board based on the values
  // of the current board
  bool[,] newBoard = new bool[BOARD_WIDTH, BOARD_LENGTH];
  for (int x = 0; x < board.GetLength(0); x++)
  {
    for (int y = 0; y < board.GetLength(1); y++)
    {
      newBoard[x,y] = CalculateCellLife(currentBoard, x, y);
    }
  }
  return newBoard;
}

bool CalculateCellLife(bool[,] board, int x, int y)
{
  int neighbors = CountNeighbors(board, x, y);
  // If cell is alive:
  if (board[x, y] == true)
  {
    // 0 or 1 neighbors: Die of loneliness
    if (neighbors <= 1)
      return false;
    // 4 or more neighbors: Die of overpopulation
    if (neighbors >= 4)
      return false;
    return true;
  }
  // If cell is not alive:
  else
  {
    // 3 neighbors give birth to a new cell
    if (neighbors == 3)
      return true;
    return false;
  }
}

int CountNeighbors(bool[,] board, int x, int y)
{
  int ac = 0;
  // Double loop from 1 up and 1 left to 1 down and 1 right
  // Gives 9 iterations
  for (int i = x - 1; i <= x + 1; i++)
  {
    for (int j = y - 1; j <= y + 1; j++)
    {
      // Only count cells within the board (IsValidIndex)
      // and do not count the cell itself (IsYourself)
      if (IsValidIndex(i, j, board.GetLength(0), board.GetLength(1)) 
          && !IsYourself(x, y, i, j) 
          && board[i, j])
        ac++;
    }
  }

  return ac;
}

bool IsValidIndex(int i, int j, int rows, int cols)
{
  return i >= 0 && i < rows && j >= 0 && j < cols;
}

bool IsYourself(int x, int y, int i, int j)
{
  return x == i && y == j;
}

void DrawCell(int x, int y, bool isAlive)
{
  Console.SetCursorPosition(x, y);
  if (!isAlive)
    Console.BackgroundColor = ConsoleColor.Black;
  else
    Console.BackgroundColor = ConsoleColor.White;
  Console.Write(" ");
}

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET | Tagged , , , , | 2 Comments