Manage configuration with Estrelica.Core.Configuration
While it is possible to handle the Archer connection process via hard-coded values and direct calls to Core.ValidateLicense() and Core.CreateSession(), it is recommended to store all of your settings in configuration files, and let the CoreConfig.Load() class handle the heavy lifting for you. This is included in the Estrelica.Core.Configuration package available via NuGet.
If you follow the convention described below in creating a JSON appSettings file, you can simply call the CoreConfig.Load() method and it will handle all of the actions above. This allows you to manage your configuration separately from your code, while also supporting various alternate configuration and override behaviors.
Here's an example appSettings.json file for the configuration values described on the Connecting To Archer page:
{
"CHSAuthKey": "5E56F79E-E678-4BAF-8FCF-B50141FAD5C7",
"Archer": {
"url": "https://archer.company.com",
"instance": "Archer-Prod",
"username": "apiuser@company.com",
"password": "Password123",
"connectionString": ""
}
}
If this configuration is stored in a file named appSettings.json in the application's current operating directory (typically the same directory where the application executable resides), you can simply call CoreConfig.Load() with just your warningCallback and it will take care of everything, returning a Core instance:
var core = Estrelica.CoreConfig.Load(wm => Log(wm.Message));
Just like when calling Core.Validate() directly as shown in the Connecting to Archer article, the Action<Exception> warningCallback parameter is required. This is how Estrelica.Core will let you know of any non-fatal issues arising during the license validation step, including notice of upcoming expiration.
If your config is stored in a different location or has a different filename, you can override the default expectation via the appConfigFilename parameter:
var core = Estrelica.CoreConfig.Load(wm => Log(wm.Message), appConfigFilename: "c:\temp\alternateSettings.json");
As the example above shows, it may be tempting to use this approach during development to override the actual appSettings.json file with alternate settings. For example, the appSettings.json file in your project directory may contain production configuration, intended for use when the app is actually deployed, but during development you'll most likely want to use a different Archer server, instance and/or credentials. Handling this scenario by specifying an alternate filename is a bad idea, however, as it leads to the risk of accidentally releasing code to production with that alternate dev-only expectation included.
User Secrets
For these "alternate configuration" scenarios, the CoreConfig.Load() method supports user secrets. This allows each developer on a project to maintain their own local app settings, independent of source control and available only on their specific development machine. This allows not only separation of dev versus prod configuration, but also separation of individual developers' credentials from one another. Furthermore, user secrets are not limited to development. You may choose to use them in production as well, so that actual production credentials need not be stored with the project in source control. Since they are maintained separately from the application this also eliminates the risk of overwriting them during an upgrade deployment.
Microsoft provides a detailed writeup of user secrets in their documentation, but that information is overly complicated so don't be put off by it. Think of user secrets as just another file (named secrets.json) which resides outside of your source tree in a particular location on each developer's machine, following the format of your appSettings.json file. There's no need to use the Secret Manager tool to manage your user secrets as described in that article, as any text editor (e.g. the Visual Studio IDE) can modify the secrets.json file. Microsoft's documentation also implies that user secrets only apply to ASP.NET applications, but in fact they can be used with any type of .NET application which references the Microsoft.Extensions.Configuration.UserSecrets assembly.
If user secrets are provided, any settings specified in the secrets.json file will override the corresponding values in appSettings.json. The secrets.json can include all values, or only those which are to be overridden.
During the configuration load process the two files are effectively merged by first loading the appSettings.json file (if present), then replacing or inserting values into the loaded configuration with any same-named values found in the user secrets file (if present).
While both files are optional (allowing you to manage your entire configuration in user secrets, if you want to), at least one must be present. If neither file exists, an exception will be raised. Futhermore, all required values (e.g. the CastleHill Software authentication key and Archer connection/credential info) must be present in the merged result. CoreConfig doesn't care where each value comes from (either appSettings.json or secrets.json), but they must be in one or the other (or both).
Implementing and managing user secrets in Visual Studio is fairly straightforward. Simply right-click on your project and choose "Manage User Secrets". This will create a new empty file in the local user's AppData folder named secrets.json, then automatically open it for you to edit in the Visual Studio editor.
Note: The first time you manage user secrets on a project, you may be presented with this dialog:
followed by an error like this:
If so, you can resolve this problem by manually installing the latest version of Microsoft.Extensions.Configuration.UserSecrets from nuget.org via the NuGet Package Manager.
The user secrets for your project are stored in a file located at "%appdata%\Microsoft\UserSecrets\xxxx\secrets.json", where "xxxx" is the User Secrets Id for the current project. This folder is not part of the Visual Studio solution, so ensures that whatever secrets are stored in the file will never get committed to source control.
User secrets also work under Linux, but they can't be managed via the VS Code IDE quite as easily as with Visual Studio under Windows. You'll need to manage the secrets.json file yourself, and ensure that it is stored in the "~/.microsoft/usersecrets/xxxx/" directory of the executing user account, where "xxxx" is the User Secrets Id for the current project.
Here's an example of an override scenario where, during development, we want to use a development instance on a different server, rather than the one that was specified in the appSettings.json file described above. To do this, we just need to save these settings in the secrets.json file:
{
"Archer": {
"url": "https://dev-archer.company.com",
"instance": "Archer-Dev"
}
}
Each user secrets file has its own Id (which is actually just the short name of the directory where the secrets.json file is stored), so this Id must be passed to the CoreConfig.Load() method in order for the overrides to be evaluated. When creating a user secrets file in Visual Studio, the Id is system-generated and can be discovered by opening the project's csproj file and examining the <UserSecretsId> node:
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UserSecretsId>1f5e7a05-94fb-44be-b10f-ff9e17ccbc55</UserSecretsId>
</PropertyGroup>
This Id can be copied and pasted into your code, passing it to the CoreConfig.Load() method via the userSecretsId parameter:
var core = Estrelica.CoreConfig.Load(wm => Log(wm.Message), userSecretsId: "1f5e7a05-94fb-44be-b10f-ff9e17ccbc55");
This will cause the Core to authenticate with Archer as above, using the settings defined in the default appSettings.json file (or a different one if overridden via the appConfigFilename parameter), but it will selectively override the values in that configuration with any found in the secrets.json file. So in this example, it will still use the apiUser@company.com/Password123 credentials from appSettings.json, but it will authenticate instead with the Archer-Dev instance on the dev-archer server, using the overrides found in the local user's secrets.json file.
Any and all settings in the appSettings.json file may be overridden via secrets.json, as long as the same format is followed. In fact, if secrets.json contains everything that's needed (the CHSAuthKey and all of the Archer settings), the appSettings.json file itself becomes optional, and the CoreConfig.Load() method won't throw an exception if appSettings.json is not found.
It's important to understand that, by design, this secrets.json file is NOT part of the Visual Studio project, and therefore will not be committed to source control. It exists only on the local user's system. This means that any other developers working on the same project MUST also go through the right-click -> "Manage User Secrets" process above in order to manage their own local configuration. However, the <UserSecretsId> node in the csproj file WILL be committed to source control, so that Id and the location of the secrets.json file will remain consistent across the team.
You may already be wondering, why do I have to specify the user secrets Id explicitly in the call to CoreConfig.Load() if it's already defined by the project itself? This is because the Id is only associated with the .csproj project file, so that Visual Studio knows which file to open when you select "Manage User Secrets". The contents of the .csproj file are not available to the compiled binary at runtime, so the user secrets Id must be explicitly passed to the method.
As shown in the example above, the user secrets Id is an auto-generated Guid, created by Visual Studio the first time "Manage User Secrets" is selected on a project. This action takes care of inserting a new <UserSecretsId> node into the csproj file as well as creating the directory structure and the file itself on the local filesystem. Guids can be awkward to work with however, so it is recommended to rename this to something more easily memorable instead.
To change the user secrets Id,
- Close any editors that currently have the secrets.json file open.
- Open the project's csproj file and take note of the current <UserSecretsId> value.
- Open File Explorer and navigate to "%appdata%\Microsoft\UserSecrets". There you will see a directory having the same name (i.e. a guid) as the <UserSecretsId> value from the previous step. This is the local directory where the secrets.json file resides.
- Rename this directory with a valid name of your choosing. This will become your new user secrets Id. A good example might be "Estrelica.Core".
- Return to the csproj file and paste the new directory name (e.g. "Estrelica.Core") into the <UserSecretsId> node. Save the csproj file.
- Right-click the project and choose "Manage User Secrets". This will re-open the secrets.json file from its new (renamed) location in the Visual Studio editor. Confirm that its contents are as expected.
With this change, using "Estrelica.Core" as the new Id, the call to CoreConfig.Load() becomes
var core = Estrelica.CoreConfig.Load(wm => Log(wm.Message),
userSecretsId: "Estrelica.Core");
Environment overrides
When implementing an integration project it's not uncommon to have to deal with multiple Archer instances, potentially hosted on multiple web servers. The examples above show how an appSettings.json file and/or user secrets.json file may be used to connect Estrelica.Core to a single Archer instance, but what if you have several?
One way to handle this would be to create separate appSettings.json and/or secrets.json files for each, and selectively pass their filenames and/or Ids into the CoreConfig.Load() method as needed when switching between instances. This is cumbersome however, and can lead to runtime errors or unexpected results if the wrong user secrets get merged into your app settings.
Estrelica.Core.Configuration handles this by supporting a second level of override behavior. In addition to the user secrets overrides discussed above, you can also selectively override any of the settings in the "Archer" node by adding additional nodes inside of it with your override values.
Expanding on the above example, imagine a scenario where you have two Archer instances (named "Archer-Prod" and "Archer-UAT") running on the same Archer server (at https://archer.company.com). Furthermore, you have the same user (apiuser@company.com) in both instances, but with different passwords in each.
This scenario can be handled by nesting the necessary overrides directly inside the Archer node, as follows.
We'll start by moving the two values that differ between the environments (instance and password) down into a new override node named "Production", nested under the standard "Archer" node in appSettings.json:
{
"CHSAuthKey": "5E56F79E-E678-4BAF-8FCF-B50141FAD5C7",
"Archer": {
"url": "https://archer.company.com",
"username": "apiuser@company.com",
"connectionString": "",
"Production": {
"instance": "Archer-Prod",
"password": "Password123"
}
}
}
Then we'll add another override node named "UAT" and put its values for those same two settings there:
{
"CHSAuthKey": "5E56F79E-E678-4BAF-8FCF-B50141FAD5C7",
"Archer": {
"url": "https://archer.company.com",
"username": "apiuser@company.com",
"connectionString": "",
"Production": {
"instance": "Archer-Prod",
"password": "Password123"
},
"UAT": {
"instance": "Archer-UAT",
"password": "v)SE(*Fh2kj;alk"
}
}
}
The "key" names used in this example ("Production" and "UAT") for the override sub-sections are arbitrary, so you may define them with whatever names make sense for your needs. The only requirements are that they must be valid JSON key strings, must be unique within the "Archer" node, and must not collide with any of the standard keys (url, instance, username, password or connectionString) in the "Archer" section.
With this type of configuration you can use another optional parameter on the CoreConfig.Load() method named "configOverrideKey" to tell it which environment-specific overrides should be used. For example, calling
var core = Estrelica.CoreConfig.Load(wm => Log(wm.Message),
configOverrideKey: "UAT");
will cause Estrelica.Core to authenticate with the "Archer-UAT" instance running on the same server as before, using the same user account (apiuser@company.com) but with "v)SE(*Fh2kj;alk" as the password rather than "Password123".
In other words, the base config will be loaded from the standard "Archer" key, then, if the "configOverrideKey" parameter is not null, and a sub node exists inside the "Archer" node having that value as its key, the values found inside that sub node will be "promoted" up to the Archer node, overriding any values it contains.
This is ideal for situations where you have multiple target environments which share one or more of these values. For example, if you have Production, UAT and Development installations of Archer, all of which have the same "XYZ" instance name and identical user credentials, but are running on three different IIS servers, you might use a configuration like this:
{
"CHSAuthKey": "5E56F79E-E678-4BAF-8FCF-B50141FAD5C7",
"Archer": {
"instance": "XYZ123",
"username": "apiuser@company.com",
"password": "v)SE(*Fh2kj;alk",
"Production": {
"url": "https://archer.company.com"
},
"UAT": {
"url": "https://archer-uat.company.com"
},
"Dev": {
"url": "http://devserver.local"
}
}
}
Then you'd simply need to specify one of the three configOverrideKey values ("Production", "UAT" or "Development") during the call to CoreConfig.Load() in order to select which installation Estrelica.Core will communicate with. (Note that in this scenario, since the "url" is only specified in the override nodes, a configOverrideKey must be provided to the Load() method. A null (default) configOverrideKey will result in an exception since there is no "url" specified under the default "Archer" node, so Estrelica.Core will not know which URL to use for its communication.)
Furthermore, these overrides may be combined with the user secrets overrides as well. This allows for team development scenarios where each team member is expected to use their own personal Archer account(s) for development and testing against multiple Archer instances. For example, you may choose to remove all usernames and passwords from your shared appSettings.json file, like so:
{
"CHSAuthKey": "5E56F79E-E678-4BAF-8FCF-B50141FAD5C7",
"Archer": {
"url": "https://archer.company.com",
"Production": {
"instance": "Archer-Prod",
},
"UAT": {
"instance": "Archer-UAT",
}
}
}
and put usernames and passwords in local user secrets, e.g.
{
"Archer": {
"username": "Developer1",
"Production": {
"password": "nbewaroija32"
},
"UAT": {
"password": "*n2*)(h2jk)(__"
}
}
}
Then by using both the "userSecretsId" and "configOverrideKey" parameters during the call to CoreConfig.Load() as follows:
var core = Estrelica.CoreConfig.Load(wm => Log(wm.Message),
userSecretsId: "Estrelica.Core",
configOverrideKey: "UAT");
this will cause Estrelica.Core to authenticate with
- the "Archer-UAT" instance (defined by the "UAT" instance override label in appSettings.json)
- located at https://archer.company.com (defined by the default "Archer" url in appSettings.json)
- using the account name "Developer1" (defined by the default "Archer" username in the user secrets file)
- and password "n2)(h2jk)(__" (defined by the password found in the "UAT" instance override settings in the user secrets file).
Even if you only have a single Archer instance, this override strategy can also be useful for testing your application under different user accounts with different roles or permissions in Archer, to see how those permissions affect your code execution. Just put all your Archer connection details in the default "Archer" section, then define override sections for each of the user accounts to be tested:
{
"CHSAuthKey": "5E56F79E-E678-4BAF-8FCF-B50141FAD5C7",
"Archer": {
"url": "https://archer.company.com",
"instance": "Archer-UAT",
"SysadminTestScenario": {
"username": "sysadmin",
"password": "@rcher@dmin"
},
"GeneralUserTestScenario": {
"username": "user123",
"password": "p@ssword321"
}
}
}
Then it's a simple matter of invoking CoreConfig.Load() alternately with configOverrideKey: "SysadminTestScenario" and configOverrideKey: "GeneralUserTestScenario" to see what differences arise in your code.