Skip to main content

C# Client Library

This document outlines the designs behind the GSF Carbon Aware C# Client Library.

Namespace

Given the fact this is going to be a library exposing functionality to consumers, will use the standard namespace naming schema: <Company>.(<Product>|<Technology>)[.<Feature>][.<Subnamespace>]. For GSF CarbonAware SDK this the following schema:

  • Company: GSF
  • Product: CarbonAware
  • Feature: Models, Handlers, ...

An example of a namespace would be: namespace GSF.CarbonAware.Models and a class (record, interface, ...) that belongs to that namespace would be:

namespace GSF.CarbonAware.Models;

public record EmissionsData
{
....
}

The following namespaces are included:

namespace
GSF.CarbonAware.Exceptions
GSF.CarbonAware.Configuration
GSF.CarbonAware.Handlers
GSF.CarbonAware.Models
GSF.CarbonAware.Parameters

Features

Models

There are two main classes that represents the data fetched from the data sources (i.e Static Json, WattTime, ElectricityMaps, and ElectricityMapsFree):

  • EmissionsData
  • EmissionsForecast

A record is defined for each of these data types owned by the library.

namespace GSF.CarbonAware.Models;
public record EmissionsData
{
string Location
DateTimeOffset Time
double Rating
TimeSpan Duration
}
namespace GSF.CarbonAware.Models;
public record EmissionsForecast
{
DateTimeOffset RequestedAt
DateTimeOffset GeneratedAt
IEnumerable<EmissionsData> EmissionsDataPoints
IEnumerable<EmissionsData> OptimalDataPoints
}

The user can expect to either have a primitive type (such as an int) or one of these specific models as a return type of the Handlers.

Handlers

There will be two handlers for each of the data types returned:

  • EmissionsHandler
  • ForecastHandler

Each is responsible for interacting on its own domain. For instance, EmissionsHandler can have a method GetAverageCarbonIntensityAsync() to pull EmissionsData data from a configured data source and calculate the average carbon intensity. ForecastHandler can have a method GetCurrentAsync(), that will return a EmissionsForecast instance. (Note: The current core implementation is using async/await paradigm, which would be the default for library too).

In addition, there is a LocationHandler that is responsible for retrieving all the locations supported by the underlying datasource.

Parameters

Both handlers require that exact fields be passed in as input. Within the docs of each library function, we specifically call out which fields the function expects to be defined versus which are optional. Internally, we handle creating the CarbonAwareParameters object and validating the fields through that.

Carbon Aware Parameters

The CarbonAwareParameters class allows the user to pass in a unique parameter instance to the public methods in the Handlers with the specific parameters needed by that call. The list of allowed parameters is defined in the class and includes

  • SingleLocation
  • MultipleLocations
  • Start
  • End
  • RequestedAt
  • Duration

Parameter Display Names

The display name of each parameter can be overriden using the public setter. By default, each parameter display name is set to the variable name (ex: Start = "start"). The parameter display names are used when creating the validation error messages. Overriding them is useful in situations where the variables the user is using for input don't exactly match the default display name of the parameter (e.g. the user variable in the controller is periodStartTime instead of startTime). That way, when the error is thrown to the user, the parameter names will match the users' expectation

To do the override, define a class that inherits from CarbonAwareParametersBaseDTO and uses the [FromQuery(Name = "myAwesomeDisplayName")] or [JsonPropertyName("myAwesomeDisplayName")] attribute. A second (less recommended) option is to pass the optional arg Dictionary<string, string>? displayNameMap when you are directly creating the object. With either option, the SDK handles updating references internally.

Required Properties

The first core check the parameters class does is validating that required parameters are defined. By default, all parameters are considered optional. Calling the SetRequiredProperties(...) function with the desired arguments sets the required parameters for the instance.

    /// <summary>
/// Accepts any PropertyNames as arguments and sets the associated property as required for validation.
/// </summary>
public void SetRequiredProperties(params PropertyName[] requiredProperties)

Validations

The second core check the parameters class does is enforcing validations on the parameters themselves. Some common examples include

  • Relationship validations: start < end must be true
  • Content validations: list.Any() must be true for list fields

Calling the SetValidations(...) function with the desired arguments sets the validations for the instance.

    /// <summary>
/// Accepts any ValidationName as arguments and sets the associated validation to check.
/// </summary>
public void SetValidations(params ValidationName[] validationNames)

Validate

Calling the Validate(...) function validates (1) required parameters and (2) specified validations. Currently, the only validation we check is whether start is before end.

If no errors are thrown, the function simply returns. If any validation errors are found, they are packaged into a single ArgumentException error with each being part of the data dictionary.

    /// <summary>
/// Validates the properties and relationships between properties. Any validation errors found are packaged into an
/// ArgumentException and thrown. If there are no errors, simply returns void.
/// </summary>
public void Validate()

Getters With Default Fallbacks

Certain parameters have special getters that allow you to define a fallback default value if the parameter is null. This can be useful in cases where a parameter is optional, so you want to get it if it was defined by the user, or otherwise fallback to a specific default. These include Start, End, Requested,and Duration

   DateTimeOffset StartOrDefault(DateTimeOffset defaultStart)
DateTimeOffset EndOrDefault(DateTimeOffset defaultEnd)
DateTimeOffset RequestedOrDefault(DateTimeOffset defaultRequested)
TimeSpan DurationOrDefault

Error Handling

The CarbonAwareException class is used to report errors to the consumer. It follows the Exception class approach, where messages and details are provided as part of error reporting.

References

https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/