Skip to content

Commit c167a75

Browse files
committed
environment variable parameters
1 parent 220e174 commit c167a75

8 files changed

Lines changed: 117 additions & 34 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# 0.20.1 <small>2026-04-30</small>
2+
3+
## 💅 Improvements
4+
- Parameters with an `env` attribute now automatically read their default value from the corresponding environment variable, but only when the caller has not already supplied a value for that parameter. Read more about it [here](./docs/environment-variables.md).
5+
6+
<!-- CHANGELOG_BOUNDARY -->
7+
18
# 0.20.0 <small>2026-04-29</small>
29

310
## 💅 Improvements

docs/environment-variables.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Environment Variable Parameters
2+
3+
Parameters can read their values from environment variables at load time. This lets you keep
4+
sensitive information — connection strings, API keys, passwords — out of your arrangements and
5+
common settings entirely.
6+
7+
## Configuration
8+
9+
Add an `env` attribute to any `<add>` element inside `<parameters>`, pointing to the environment
10+
variable whose value should be used:
11+
12+
```xml
13+
<parameters>
14+
<add name="ConnectionString" env="MY_DB_CONNECTION_STRING" />
15+
<add name="ApiKey" env="MY_API_KEY" />
16+
</parameters>
17+
```
18+
19+
When the arrangement is loaded, if no external caller has already supplied a value for the
20+
parameter, the module reads the named environment variable and uses it as the parameter's value.
21+
22+
## How It Works
23+
24+
- If a caller (e.g. a form submission or a query-string value) already provides a value for
25+
the parameter, the environment variable is **not** consulted — caller-supplied values take
26+
precedence.
27+
- If the parameter has no value and `env` is set, `Environment.GetEnvironmentVariable(env)` is
28+
called and the result becomes the parameter's value for that request.
29+
- If the environment variable is not set (returns `null`), the parameter remains empty.
30+
31+
## Where It Applies
32+
33+
Environment variable resolution runs in both contexts:
34+
35+
- **Common settings** — the shared arrangement loaded from the module's site settings.
36+
- **Content item arrangements** — per-content-item XML/JSON arrangements.
37+
38+
## Typical Use Case
39+
40+
Store a database password in an environment variable on the server and reference it in your
41+
arrangement without ever writing the literal value into Orchard content or settings:
42+
43+
```xml
44+
<connections>
45+
<add name="input"
46+
provider="sqlserver"
47+
server="myserver"
48+
database="mydb"
49+
user="myuser"
50+
password="@[DbPassword]" />
51+
</connections>
52+
<parameters>
53+
<add name="DbPassword" env="DB_PASSWORD" />
54+
</parameters>
55+
```
56+
57+
Setting `input="false"` ensures the parameter is never exposed to end-user input — it is
58+
resolved from the environment and injected into the arrangement internally.

src/OrchardCore.Transformalize/Common.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ public static class Common {
1515
public const string OrchardConnectionName = "orchard";
1616
public const string Default = "[default]";
1717

18+
public const char PlaceHolderMarker = '@';
19+
public const char PlaceHolderOpen = '[';
20+
public const char PlaceHolderClose = ']';
21+
1822
public const string TaskReferrer = "TaskReferrer";
1923
public const string TaskContentItemId = "TaskContentItemId";
2024
public const string ReportContentItemId = "ReportContentItemId";

src/OrchardCore.Transformalize/OrchardCore.Transformalize.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
<TargetFramework>net10.0</TargetFramework>
44
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
55
<RootNamespace>TransformalizeModule</RootNamespace>
6-
<Version>0.20.0</Version>
7-
<FileVersion>0.20.0</FileVersion>
8-
<AssemblyVersion>0.20.0</AssemblyVersion>
6+
<Version>0.20.1</Version>
7+
<FileVersion>0.20.1</FileVersion>
8+
<AssemblyVersion>0.20.1</AssemblyVersion>
99
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
1010
<Authors>Dale Newman</Authors>
1111
<Copyright>Copyright © 2013-2026</Copyright>

src/OrchardCore.Transformalize/Services/Modifiers/ParameterModifier.cs

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class ParameterModifier : ICustomizer {
1717
private const string ParameterNameAttribute = "name";
1818
private const string ParameterValueAttribute = "value";
1919
private const string ParameterInputAttribute = "input";
20+
private const string ParameterEnvAttribute = "env";
2021

2122
public ParameterModifier(
2223
IPlaceHolderReplacer placeHolderReplacer
@@ -64,34 +65,48 @@ private void MergeParameters(IEnumerable<INode> nodes, IDictionary<string, strin
6465
foreach (var parameter in nodes) {
6566
string name = null;
6667
object value = null;
68+
string env = null;
6769
foreach (var attribute in parameter.Attributes) {
6870
if (attribute.Name == ParameterNameAttribute) {
6971
name = attribute.Value.ToString();
7072
} else if (attribute.Name == ParameterValueAttribute) {
7173
value = attribute.Value;
74+
} else if (attribute.Name == ParameterEnvAttribute) {
75+
env = attribute.Value?.ToString();
7276
}
7377
}
74-
if (name != null && value != null) {
7578

79+
if (name == null) continue;
7680

77-
if (parameters.ContainsKey(name)) {
78-
// arrangement and external parameter match
81+
if (parameters.ContainsKey(name)) {
82+
// arrangement and external parameter match
7983

80-
// if the arrangment parameter says input is false, we remove it as it is not permitted
81-
if (parameter.TryAttribute(ParameterInputAttribute, out var inputAttr) && inputAttr.Value.Equals("false")) {
82-
parameters.Remove(name);
83-
continue;
84-
}
84+
// if the arrangement parameter says input is false, we remove it as it is not permitted
85+
if (parameter.TryAttribute(ParameterInputAttribute, out var inputAttr) && inputAttr.Value.Equals("false")) {
86+
parameters.Remove(name);
87+
continue;
88+
}
89+
90+
// the external parameter will set the arrangement parameter's value attribute
91+
if (parameter.TryAttribute(ParameterValueAttribute, out var valueAttr)) {
92+
valueAttr.Value = parameters[name];
93+
} else {
94+
parameter.Attributes.Add(new Attribute("value", parameters[name]));
95+
}
96+
97+
} else {
98+
// caller hasn't provided a value — resolve from env var if value is absent/empty
99+
var effectiveValue = string.IsNullOrWhiteSpace(value?.ToString()) && !string.IsNullOrEmpty(env)
100+
? System.Environment.GetEnvironmentVariable(env)
101+
: value?.ToString();
85102

86-
// the external parameter will set the arrangement parameter's value attribute
103+
if (effectiveValue != null) {
104+
parameters[name] = effectiveValue;
87105
if (parameter.TryAttribute(ParameterValueAttribute, out var valueAttr)) {
88-
valueAttr.Value = parameters[name];
106+
valueAttr.Value = effectiveValue;
89107
} else {
90-
parameter.Attributes.Add(new Attribute("value", parameters[name]));
108+
parameter.Attributes.Add(new Attribute("value", effectiveValue));
91109
}
92-
93-
} else { // attribute value is going to set the parameter
94-
parameters[name] = value.ToString();
95110
}
96111
}
97112
}

src/OrchardCore.Transformalize/Services/OrchardConfigurationContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public async Task<ILifetimeScope> CreateScopeAsync(string arrangement, ContentIt
8787
dependancies.Add(Serializer);
8888
}
8989

90-
dependancies.Add(new ParameterModifier(new PlaceHolderReplacer('@', '[', ']')));
90+
dependancies.Add(new ParameterModifier(new PlaceHolderReplacer(Common.PlaceHolderMarker, Common.PlaceHolderOpen, Common.PlaceHolderClose)));
9191

9292
// these were registered by the ShorthandModule are are used to expand shorthand transforms and validators into "longhand".
9393
dependancies.Add(ctx.ResolveNamed<IDependency>(TransformModule.FieldsName));

src/OrchardCore.Transformalize/Services/SettingsService.cs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
using TransformalizeModule.Models;
22
using TransformalizeModule.Services.Contracts;
3-
using OrchardCore.ContentManagement;
4-
using OrchardCore.Entities;
3+
using TransformalizeModule.Services.Modifiers;
54
using OrchardCore.Settings;
6-
using System;
7-
using System.Collections.Generic;
8-
using System.Linq;
95
using Transformalize.Configuration;
106
using StackExchange.Profiling;
117
using TransformalizeModule.Ext;
@@ -20,9 +16,14 @@ namespace TransformalizeModule.Services {
2016
/// </summary>
2117
public class SettingsService : ISettingsService {
2218

19+
public TransformalizeSettings Settings { get; }
20+
private readonly IDbConnectionAccessor _dbConnectionAccessor;
21+
private readonly IStore _store;
22+
private readonly CombinedLogger<SettingsService> _logger;
23+
2324
public Process Process { get; set; }
2425
public Transformalize.ConfigurationFacade.Process ProcessFacade { get; set; }
25-
public TransformalizeSettings Settings { get; }
26+
2627
public Dictionary<string, Parameter> Parameters { get; } = new Dictionary<string, Parameter>();
2728
private readonly Dictionary<string, Transformalize.ConfigurationFacade.Parameter> ParametersFacade = new Dictionary<string, Transformalize.ConfigurationFacade.Parameter>(StringComparer.OrdinalIgnoreCase);
2829

@@ -38,10 +39,6 @@ public class SettingsService : ISettingsService {
3839
public Dictionary<string, Field> Fields { get; } = new Dictionary<string, Field>();
3940
private readonly Dictionary<string, Transformalize.ConfigurationFacade.Field> FieldsFacade = new Dictionary<string, Transformalize.ConfigurationFacade.Field>(StringComparer.OrdinalIgnoreCase);
4041

41-
private readonly IDbConnectionAccessor _dbConnectionAccessor;
42-
private readonly IStore _store;
43-
private readonly CombinedLogger<SettingsService> _logger;
44-
4542
public SettingsService(
4643
ISiteService siteService,
4744
IDbConnectionAccessor dbConnectionAccessor,
@@ -62,8 +59,9 @@ CombinedLogger<SettingsService> logger
6259
Process = new Process();
6360
ProcessFacade = new Transformalize.ConfigurationFacade.Process();
6461
} else {
65-
Process = new Process(Settings.CommonArrangement);
66-
ProcessFacade = new Transformalize.ConfigurationFacade.Process(Settings.CommonArrangement);
62+
var modifier = new ParameterModifier(new Cfg.Net.Environment.PlaceHolderReplacer(Common.PlaceHolderMarker, Common.PlaceHolderOpen, Common.PlaceHolderClose));
63+
Process = new Process(Settings.CommonArrangement, modifier);
64+
ProcessFacade = new Transformalize.ConfigurationFacade.Process(Settings.CommonArrangement, dependencies: [modifier]);
6765
}
6866

6967
// parameters
@@ -343,5 +341,6 @@ public void ApplyCommonSettings(Transformalize.ConfigurationFacade.Process proce
343341
}
344342

345343
}
344+
346345
}
347346
}

src/Site/Site.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
88
<DockerfileContext>..\..</DockerfileContext>
99
<DockerfileTag>transformalize.orchard</DockerfileTag>
10-
<Version>0.20.0</Version>
11-
<FileVersion>0.20.0</FileVersion>
12-
<AssemblyVersion>0.20.0</AssemblyVersion>
13-
<ReleaseVersion>0.20.0</ReleaseVersion>
10+
<Version>0.20.1</Version>
11+
<FileVersion>0.20.1</FileVersion>
12+
<AssemblyVersion>0.20.1</AssemblyVersion>
13+
<ReleaseVersion>0.20.1</ReleaseVersion>
1414
<RazorRuntimeCompilation>true</RazorRuntimeCompilation>
1515
<CopyRefAssembliesToPublishDirectory>true</CopyRefAssembliesToPublishDirectory>
1616
<Nullable>enable</Nullable>

0 commit comments

Comments
 (0)