LogoLogo
HomePricingDocumentation
  • 💿Getting Started
    • Installation and Project Setup
    • Hello Perigee!
    • Perigee Application Design
    • Hello Configuration
    • Hello Logs
    • Hello Integration
    • Troubleshooting
    • Case Studies
  • 📃License + Notice
    • 📂Licensing
    • Notice of Third Party Agreements
  • 🚀Perigee and Beyond
    • Extending - Threads
    • Extending - Loaders
    • ⏳All about CRON
  • 🔮API Generation
    • What is API Generation?
    • API Builder
  • 🗺️Architecting YOUR App
    • Design and Requirements
    • Define Sources
    • Requirements
  • 🧩Core Modules
    • 🌐PerigeeApplication
    • 🪡Thread Registry
    • Event Sources
      • Scheduled/Logic
        • CRON Thread
        • Scheduler
        • Sync Agent
      • Watchers
        • SalesForce
        • Sharepoint
        • Directory Watch
        • Directory Notifier
        • IMAP
    • Credential Management
      • Connection Strings
      • Custom Refresh Logic
      • RestSharp Authenticator
      • Credential Store SDK
      • ⁉️Troubleshooting Credentials
    • Integration Utilities
      • HTTP(S) - RestSharp
      • Transaction Coordinator
      • Limiter
      • Watermarking
    • Alert Managers
      • SMS
      • Email
      • Discord
      • Teams
    • File Formats
      • Excel
      • CSV
    • 📁File System Storage
      • File Revision Store
      • Concurrent File Store
      • FileSync + Cache
    • Third Party
      • SmartSheets
      • Microsoft Graph
    • Perigee In Parallel
      • Parallel Processing Reference
      • Extensions
      • GroupProcessor
      • SingleProcessor
    • 🧱Utility Classes
      • Metrics
      • F(x) Expressions
      • Multi-Threaded Processor (Scatter Gather)
      • OpenAI - GPT
      • XML Converter
      • Dynamic Data Table
      • Debounce
      • Thread Conditions
      • Perigee Utility Class
      • Network Utility
      • Lists
      • FileUtil
      • Inclusive2DRange
      • Strings, Numbers, Dates
      • Nested Sets
      • Behavior Trees
      • JsonCompress
      • Topological Sorting
      • DBDownloader
    • 🈁Bit Serializer
  • 📣Examples and Demos
    • API + Perigee
    • 📰Excel Quick Load
    • SalesForce Watcher
    • Report Scheduler
    • Agent Data Synchronization
    • 📩IMAP Echo bot
    • Watch and load CSVs
    • Graph Delegated Authorization + DataVerse
    • Coordinator Demo
    • Azure Service Bus
    • QuickBooks Online
  • 📘Blueprints
    • Perigee With .NET Hosting
    • Web Host Utilities
    • 🔌Plugin Load Context
  • 🎞️Transforms
    • 🌟What is Transforms?
    • 📘Terminology
    • 🦾The Mapping Document
    • 👾Transformation Process
    • 😎Profile
    • 🎒Automation
      • 🕓Package Options
      • 🔳Configuration
    • 🔧Utilities
      • 🧹Clean
      • 📑Map File
      • 🔎File Identification
      • 🗺️Map Generation
      • 🪅Insert Statement Generation
  • 🗃️Transform SDK
    • 👋Quick Start Guide
    • 🥳MapTo
    • 🔌Authoring Plugins
      • 🔘File IO Process
      • 📢Data Quality
      • 🟢Transform Process
    • SDK Reference
      • 🔘FileIOProcessData
      • 📢DataQualityContext
      • 🎛️TransformDataContext
      • 🏅TransformResult
Powered by GitBook
On this page
  • Resources
  • Intro
  • Demo Application
  • Configuration Direct Read
  • Configuration Binding
  • Link to Config
  • Configuring the logging
  • Secure Values
  • Summary
Export as PDF
  1. Getting Started

Hello Configuration

PreviousPerigee Application DesignNextHello Logs

Last updated 11 months ago

Resources

Intro

Lets take a closer look at how the configuration works within Perigee.

To understand the configuration process we need to understand how the sections work and why we separate them. To keep it concise, sections create a logical separation of values for the different processes in our application.

As an example we have a section called Serilog. This section is entirely dedicated to setting up our logging, where we send logs to and in what format. It wouldn't make any sense to put a message queue URL or an API key inside of this section.

We create a logical separation between our logging needs and a specific threads' requirements by keeping those configuration values separate.

Configuration sections can be registered in our application in a number of ways including but not limited to:

  • Environment variables

  • main(string[] args)

  • appsettings.json file

  • Custom providers

{
  "ConnectionStrings": {
  
  },
  "AppSettings": {
  
  },
  "Perigee": { "HideConsole": false },
  "Serilog": {
    "MinimumLevel": "Debug",
    "WriteTo": [
      { "Name": "Console" }
    ]
  }
}

Line 2 - ConnectionStrings is exactly as it sounds, it's the place to put connections to other services like a database.

Line 5 - AppSettingsis the default included location to put configuration values. Various ways of retrieving configuration values will default to this section

Line 8 - Perigeeis the section automatically read by Perigee itself. As an example, if you're running in a windows console you can hide it by setting the HideConsole flag to true.

Demo Application

Our goal is to write an application that logs out our custom configuration sections. We will do so by reading the configuration directly at runtime as well as binding the configuration section to a custom class.

Configuration Direct Read

To start, we need to add another section to our appsettings.json file called HelloConfig and give it some values to read.

{
  "ConnectionStrings": {
  
  },
  "AppSettings": {
  
  },
  "HelloConfig": {
    "Enabled": true,
    "Name": "HAL 9000",
    "Year": 2001,
    "Tags": [ "Heuristic", "Algorithmic" ]
  },
  "Perigee": { "HideConsole": false },
  "Serilog": {
    "MinimumLevel": "Debug",
    "WriteTo": [
      { "Name": "Console" }
    ]
  }
}

This creates a section for us to read in our code. We will read it directly by calling taskConfig.GetValue<>().

This handy method is a quick way to reference ALL of the incoming configuration providers and it works by supplying two things:

  1. The C# Type - This could be a string, or int, or whatever other value type you're reading.

  2. A special format: Section:Key.

To read the Name from our configuration section the Type would be string and our Section:Key would be: HelloConfig:Name

Here's a fully working Perigee application

PerigeeApplication.ApplicationNoInit("HelloConfig", (taskConfig) => {

    taskConfig.AddRecurring("TestMethod", (ct, log) => {

        log.LogInformation("What is my name? It is {name}", taskConfig.GetValue<string>("HelloConfig:Name"));

    });
});
  • Line 1 - This is a simplified Perigee start method that skips IPC tokens and initialization actions.

  • Line 3 - AddRecurring simply adds a managed thread that is automatically called every n milliseconds (supplied by an optional parameter, default 5000 (5 seconds))

  • Line 5 - We log out the configuration value reading it live at runtime.

The regular .Add() method doesn't recur. If the block exits then Perigee will consider that thread to be ended and it will enter its restart phase and conditions.

This is why we use .AddRecurring() here, because the efficient loop is automatically built into it.

If a cancellation request comes through, it will not call the block again and end. You can always use the same built in loop in your own code and it's what we recommend if you're adding your own managed threads.

while (PerigeeApplication.delayOrCancel(delay, cancelToken)) { 
  // This is only called after delay, and if it's not cancelled
  // Otherwise, the loop block ends
}

You'll see below the results of running this application. We get a log every 5 seconds with the value captured from our configuration file.

Configuration Binding

Next let's look at binding the section we added to a custom class. Here's the file if you prefer to import it.

If you import the class file above, don't forget to change the namespace if you want it to auto-link to whatever your applications default namespace is.

The class simply has 3 properties that are an exact case sensitive name match to the configuration section we created.

public class HelloConfig
{
    public string Name { get; set; } // HelloConfig:Name
    public int Year { get; set; } // HelloConfig:Year
    public List<string> Tags { get; set; } // HelloConfig:Tags
}

To bind a class, simply use the taskConfig and call GetConfigurationAs<>():

taskConfig.GetConfigurationAs<HelloConfig>("HelloConfig");
  • The Generic T Parmater, HelloConfig, is the class we're creating and assigning. It's a type reference.

  • The string, "HelloConfig", is what section to get from our configuration so that we can assign it's properties to the referenced type.

This is all that is needed to bind our configuration section to a class. Here's the full application up to this point!

PerigeeApplication.ApplicationNoInit("HelloConfig", (taskConfig) => {
   
    taskConfig.AddRecurring("TestMethod", (ct, log) => {

        //Directly by reading
        log.LogInformation("What is my name? It is {name}", taskConfig.GetValue<string>("HelloConfig:Name"));

        //Binding a class
        HelloConfig config = taskConfig.GetConfigurationAs<HelloConfig>("HelloConfig");
        log.LogInformation("{name} first appeared in {year:N0} and was {@tagged}", config.Name, config.Year, config.Tags);

    });
});

Line 10 - We got the configuration as a custom class by binding to it.

Line 11 - We use the custom classes properties to write to our log.

Curious what those special characters are in the template string?

The first one, :N0 tells the template string to treat it's input as a number with 0 precision, adding the grouping character (thousands separator). This is why the logged out version has a thousands separator.

The second one has an @ symbol which tells the template string to deconstruct the input. This is why you see the array of tags written out.

Link to Config

Perigee will, by default, hot-reload the appsettings.json files. This is a fantastic way to give control to a configuration file to enable or disable a thread without shutting down the entire application.

Perigee has a great helper method built into it which enables the thread-to-configuration linking for enabling or disabling managed threads through a configuration boolean.

Our example config section we added (HelloConfig) which has a key called Enabled, and we're going to use that Boolean to tell Perigee whether or not to start or stop our thread.

PerigeeApplication.ApplicationNoInit("HelloConfig", (taskConfig) => {
   
    taskConfig.AddRecurring("TestMethod", (ct, log) => {

        //Directly by reading
        log.LogInformation("What is my name? It is {name}", taskConfig.GetValue<string>("HelloConfig:Name"));

        //Binding a class
        HelloConfig config = taskConfig.GetConfigurationAs<HelloConfig>("HelloConfig");
        log.LogInformation("{name} first appeared in {year:N0} and was {@tagged}", config.Name, config.Year, config.Tags);

    }, started: false).LinkToConfig("HelloConfig:Enabled");
});

Line 12 - We added started: false to the methods optional parameter so that with the addition of the linking command, it will be in charge of starting or stopping this thread. LinkToConfig was added and we supplied our Section:Key to it.

Because configurations are loaded, hot-reloaded, and maintained internally by the configuration system, please also use the started: false flag on the thread itself. This will prevent the thread from starting while the configuration value is "false".

The "debugging" version of the app config should be at: {projectRoot}\bin\Debug\net{version}\appsettings.json

If you change the appsettings.json file in your project you aren't actually modifying the version of the file the debugged application is reading.

Change Notification

If you'd prefer more control over how to start or stop your threads, or even make different decisions based on the configuration changes, you can always subscribe the change notification directly. .LinkToConfig() is essentially the same code as below but managed by Perigee internally and automatically, saving you from a few lines of code.

taskConfig.Event_ConfigurationUpdated += (sender, Configuration) =>
{
    bool enabled = Configuration.GetValue<bool>("HelloConfig:Enabled", false);
    if (enabled) taskConfig.StartIfNotRunning("TestMethod");
    else taskConfig.QueueStop("TestMethod", true);
};

Configuring the logging

Perigee will automatically add an additional config property block to each of it's loggers that it passes to the managed threads. This property block is called ThreadName. As a fun aside, let's update our logging template to include this property block.

Open the appsettings.json file and under the Serilog section, update the console sink to include the updated outputTemplate.

{
  "Name": "Console",
  "Args": {
    "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}]({ThreadName}) {Message:lj}{NewLine}{Exception}"
  }
}

Now our application's logs have the name of the managed thread they are coming from right in the log!

Secure Values

Perigee has several ways built in that allow you to encrypt your configuration values which may provide an extra layer of security/obscurity. It uses AES256 bit encryption on the values for secure encrypting and decrypting values and has built in methods for reading them.

To secure a value you must provide the encryption key, encryption IV, and the source of where to find these values.

There are 3 sources for the Encryption keys

  • Environment Variable source

  • Arguments Source

  • Variable Source

Here's an example of setting this up all within code using the variable source. The methods are exactly the same though if you decide to pass in the encryption key from an argument, or read it from an environment variable.

PerigeeApplication.ApplicationNoInit("DemoApp", (c) => {

//Set the value source as variables, to which we assign next
c.SecureValueSource = ThreadRegistry.SecureKeyStorage.Variable;

//Create two new randoms, one AES 256 and the IV as AES 128
c.SecureValueKey = AesCrypto.GetNewRandom();
c.SecureValueIV = AesCrypto.GetNewRandom(16);

//Write out the two new generated values, so we can use them to decrypt values with later
c.GetLogger<Program>().LogInformation($"Copy these values out!!{Environment.NewLine}Key: {{key}}{Environment.NewLine}iv: {{iv}}", c.SecureValueKey, c.SecureValueIV);

});

With that done you should see something like this printed to the console:

These are your two encryption keys, and we will use them to decrypt values in the next step. Remember you can easily put these in the environment variables, command line arguments, or even set the variables in code. For the ease of this demonstration, we'll assign them directly:

PerigeeApplication.ApplicationNoInit("DemoApp", (c) => {

//Assign our keys
c.SecureValueSource = ThreadRegistry.SecureKeyStorage.Variable;
c.SecureValueKey = "e7zP37543Rbvn5a6NnN3FGlhPVAsdTmljcXZoTLOlkw=";
c.SecureValueIV = "UFn5LH+RLLCVkxt+qfjWKQ==";

//Encrypt and decrypt the same string
string encrypted = c.EncryptSecureValue("Hello World");
string decrypted = c.DecryptSecureValue(encrypted);
c.GetLogger<Program>().LogInformation($"Encrypted: {{Encrypted}}{Environment.NewLine}Decrypted: {{Decrypted}}", encrypted, decrypted);

});

If you're reading encrypted configuration values, there's a handy little helper than does the read and decrypt in one pass:

PerigeeApplication.ApplicationNoInit("DemoApp", (c) => {

    c.GetSecureConfigurationValue("HelloConfig:EncryptedValue")
    
});

Summary

Hopefully you understand a bit better about how configuration, sections, and providers work as well as how to use them in code.

PRO TIP: If you plan to support hot-reloading, never cache the configuration values in such a way that a hot-reload would be ignored. If you're binding a configuration section to a class do so at the time you need it and dispose of the class when you're finished so that in the event the configuration section is changed between iterations of your code you get the updated values by rebinding.

There is a custom provider shipped with Perigee called the SQLConfigurationSource. It allows you to quickly wire up a MSSQL database table as a configuration section to use in your application.

Back in the we used a configuration file. A section is a key off of the root json node and as you can see there are 4 different sections in our startup file.

Line 9 - Serilogis the configuration section for Serilog logging. There's a lot of info on this section, and we'll cover a lot more of them in

You will also see some helpful ways you can plug the configuration system into

Notice the comma after the section? Make sure when you add new sections you keep the file valid JSON

Now if you run the application and navigate over to the debug folder to open up the appsettings.json file, you can change that HelloConfig:Enabled value from true to false or false to true and watch Perigee start and gracefully stop that thread

To see other thread operations, see .

If you want to read up on all of the configurations for the console, simply go to that sinks .

You can add your own properties to the loggers, we'll show you how on the page.

There's always an option of rolling your own or using custom methods of reading/decrypting values. If you want to see how to then check out the link!

🖖
🧙
🪄
Hello Logs
dedicated support page
Hello Logs
implement a custom property loader
💿
Page cover image
You can see this example here
managed threads
Installation and Project Setup
Want to watch the config and linking instead?
ThreadRegistry
2KB
2_Configuration.zip
archive
Completed part 2 code
273B
HelloConfig.cs
This is the final screenshot after all sections below have been completed. If your log doesn't look like this yet, keep going :)