Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions src/Components/Components/src/EnvironmentComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.Extensions.Hosting;

namespace Microsoft.AspNetCore.Components;

/// <summary>
/// A component that renders its child content only when the current hosting environment
/// matches one of the specified environment names.
/// </summary>
/// <remarks>
/// <para>
/// This component is similar to the environment tag helper in MVC and Razor Pages.
/// </para>
/// <example>
/// The following example shows how to conditionally render content based on the environment:
/// <code>
/// &lt;Environment Include="Development"&gt;
/// &lt;div class="alert alert-warning"&gt;
/// You are running in Development mode. Debug features are enabled.
/// &lt;/div&gt;
/// &lt;/Environment&gt;
///
/// &lt;Environment Include="Development, Staging"&gt;
/// &lt;p&gt;This is a pre-production environment.&lt;/p&gt;
/// &lt;/Environment&gt;
///
/// &lt;Environment Exclude="Production"&gt;
/// &lt;p&gt;Debug information: @DateTime.Now&lt;/p&gt;
/// &lt;/Environment&gt;
/// </code>
/// </example>
/// </remarks>
public sealed class Environment : ComponentBase
{
private static readonly char[] NameSeparator = [','];

[Inject]
private IHostEnvironment HostEnvironment { get; set; } = default!;

/// <summary>
/// Gets or sets a comma-separated list of environment names in which the content should be rendered.
/// If the current environment is also in the <see cref="Exclude"/> list, the content will not be rendered.
/// </summary>
/// <remarks>
/// The specified environment names are compared case insensitively.
/// </remarks>
[Parameter]
public string? Include { get; set; }

/// <summary>
/// Gets or sets a comma-separated list of environment names in which the content will not be rendered.
/// </summary>
/// <remarks>
/// The specified environment names are compared case insensitively.
/// </remarks>
[Parameter]
public string? Exclude { get; set; }

/// <summary>
/// Gets or sets the content to be rendered when the environment matches.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }

/// <inheritdoc />
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
if (ShouldRenderContent())
{
builder.AddContent(0, ChildContent);
}
}

private bool ShouldRenderContent()
{
var currentEnvironmentName = HostEnvironment.EnvironmentName?.Trim();

if (string.IsNullOrEmpty(currentEnvironmentName))
{
return false;
}

// Check exclusions first - if current environment is excluded, don't render
if (!string.IsNullOrWhiteSpace(Exclude))
{
foreach (var environment in ParseEnvironmentNames(Exclude))
{
if (environment.Equals(currentEnvironmentName, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
}

// If no inclusions specified, render (unless excluded above)
if (string.IsNullOrWhiteSpace(Include))
{
return true;
}

// Check if current environment is in the include list
var hasEnvironments = false;
foreach (var environment in ParseEnvironmentNames(Include))
{
hasEnvironments = true;
if (environment.Equals(currentEnvironmentName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}

// If Include was specified but contained no valid environments, render content
// (same behavior as when Include is not specified)
if (!hasEnvironments)
{
return true;
}

return false;
}

private static IEnumerable<string> ParseEnvironmentNames(string names)
{
foreach (var segment in names.Split(NameSeparator, StringSplitOptions.RemoveEmptyEntries))
{
var trimmed = segment.Trim();
if (trimmed.Length > 0)
{
yield return trimmed;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<Import Project="Microsoft.AspNetCore.Components.Routing.targets" />

<ItemGroup>
<Reference Include="Microsoft.Extensions.Hosting.Abstractions" />
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
Expand Down
8 changes: 8 additions & 0 deletions src/Components/Components/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
#nullable enable
Microsoft.AspNetCore.Components.Environment
Microsoft.AspNetCore.Components.Environment.ChildContent.get -> Microsoft.AspNetCore.Components.RenderFragment?
Microsoft.AspNetCore.Components.Environment.ChildContent.set -> void
Microsoft.AspNetCore.Components.Environment.Environment() -> void
Microsoft.AspNetCore.Components.Environment.Exclude.get -> string?
Microsoft.AspNetCore.Components.Environment.Exclude.set -> void
Microsoft.AspNetCore.Components.Environment.Include.get -> string?
Microsoft.AspNetCore.Components.Environment.Include.set -> void
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@ private static void ThrowForCaptureUnmatchedValuesConflict(Type targetType, stri
{
throw new InvalidOperationException(
$"The property '{parameterName}' on component type '{targetType.FullName}' cannot be set explicitly " +
$"when also used to capture unmatched values. Unmatched values:" + Environment.NewLine +
string.Join(Environment.NewLine, unmatched.Keys));
$"when also used to capture unmatched values. Unmatched values:" + System.Environment.NewLine +
string.Join(System.Environment.NewLine, unmatched.Keys));
}

[DoesNotReturn]
Expand All @@ -260,8 +260,8 @@ private static void ThrowForMultipleCaptureUnmatchedValuesParameters([Dynamicall
throw new InvalidOperationException(
$"Multiple properties were found on component type '{targetType.FullName}' with " +
$"'{nameof(ParameterAttribute)}.{nameof(ParameterAttribute.CaptureUnmatchedValues)}'. Only a single property " +
$"per type can use '{nameof(ParameterAttribute)}.{nameof(ParameterAttribute.CaptureUnmatchedValues)}'. Properties:" + Environment.NewLine +
string.Join(Environment.NewLine, propertyNames));
$"per type can use '{nameof(ParameterAttribute)}.{nameof(ParameterAttribute.CaptureUnmatchedValues)}'. Properties:" + System.Environment.NewLine +
string.Join(System.Environment.NewLine, propertyNames));
}

[DoesNotReturn]
Expand Down
Loading
Loading