File: RouteOptions.cs
Web Access
Project: src\src\Http\Routing\src\Microsoft.AspNetCore.Routing.csproj (Microsoft.AspNetCore.Routing)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#if !COMPONENTS
using System.Diagnostics;
#else
using Microsoft.AspNetCore.Components.Routing;
#endif
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Routing.Constraints;
 
namespace Microsoft.AspNetCore.Routing;
 
#if !COMPONENTS
/// <summary>
/// Represents the configurable options on a route.
/// </summary>
public class RouteOptions
#else
internal class RouteOptions
#endif
{
    private IDictionary<string, Type> _constraintTypeMap = GetDefaultConstraintMap();
#if !COMPONENTS
    private ICollection<EndpointDataSource> _endpointDataSources = default!;
 
    /// <summary>
    /// Gets a collection of <see cref="EndpointDataSource"/> instances configured with routing.
    /// </summary>
    internal ICollection<EndpointDataSource> EndpointDataSources
    {
        get
        {
            Debug.Assert(_endpointDataSources != null, "Endpoint data sources should have been set in DI.");
            return _endpointDataSources;
        }
        set => _endpointDataSources = value;
    }
 
    /// <summary>
    /// Gets or sets a value indicating whether all generated paths URLs are lowercase.
    /// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings.
    /// </summary>
    public bool LowercaseUrls { get; set; }
 
    /// <summary>
    /// Gets or sets a value indicating whether a generated query strings are lowercase.
    /// This property will not be used unless <see cref="LowercaseUrls" /> is also <c>true</c>.
    /// </summary>
    public bool LowercaseQueryStrings { get; set; }
 
    /// <summary>
    /// Gets or sets a value indicating whether a trailing slash should be appended to the generated URLs.
    /// </summary>
    public bool AppendTrailingSlash { get; set; }
 
    /// <summary>
    /// Gets or sets a value that indicates if the check for unhandled security endpoint metadata is suppressed.
    /// <para>
    /// Endpoints can be associated with metadata such as authorization, or CORS, that needs to be
    /// handled by a specific middleware to be actionable. If the middleware is not configured, such
    /// metadata will go unhandled.
    /// </para>
    /// <para>
    /// When <see langword="false"/>, prior to the execution of the endpoint, routing will verify that
    /// all known security-specific metadata has been handled.
    /// Setting this property to <see langword="true"/> suppresses this check.
    /// </para>
    /// </summary>
    /// <value>Defaults to <see langword="false"/>.</value>
    /// <remarks>
    /// This check exists as a safeguard against accidental insecure configuration. You may suppress
    /// this check if it does not match your application's requirements.
    /// </remarks>
    public bool SuppressCheckForUnhandledSecurityMetadata { get; set; }
#endif
 
    /// <summary>
    /// Gets or sets a collection of constraints on the current route.
    /// </summary>
    public IDictionary<string, Type> ConstraintMap
    {
        [RequiresUnreferencedCode($"The linker cannot determine what constraints are being added via the ConstraintMap property. Prefer {nameof(RouteOptions)}.{nameof(SetParameterPolicy)} instead for setting constraints. This warning can be suppressed if this property is being used to read or delete constraints.")]
        get
        {
            return _constraintTypeMap;
        }
        set
        {
            ArgumentNullException.ThrowIfNull(value);
 
            _constraintTypeMap = value;
        }
    }
 
    /// <summary>
    /// <see cref="SetParameterPolicy{T}(string)"/> ensures that types are added to the constraint map in a trimmer safe way.
    /// This API allows reading the map without encountering a trimmer warning within the framework.
    /// </summary>
    internal IDictionary<string, Type> TrimmerSafeConstraintMap => _constraintTypeMap;
 
    private static IDictionary<string, Type> GetDefaultConstraintMap()
    {
        var defaults = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
 
        // Type-specific constraints
        AddConstraint<IntRouteConstraint>(defaults, "int");
        AddConstraint<BoolRouteConstraint>(defaults, "bool");
        AddConstraint<DateTimeRouteConstraint>(defaults, "datetime");
        AddConstraint<DecimalRouteConstraint>(defaults, "decimal");
        AddConstraint<DoubleRouteConstraint>(defaults, "double");
        AddConstraint<FloatRouteConstraint>(defaults, "float");
        AddConstraint<GuidRouteConstraint>(defaults, "guid");
        AddConstraint<LongRouteConstraint>(defaults, "long");
 
        // Length constraints
        AddConstraint<MinLengthRouteConstraint>(defaults, "minlength");
        AddConstraint<MaxLengthRouteConstraint>(defaults, "maxlength");
        AddConstraint<LengthRouteConstraint>(defaults, "length");
 
        // Min/Max value constraints
        AddConstraint<MinRouteConstraint>(defaults, "min");
        AddConstraint<MaxRouteConstraint>(defaults, "max");
        AddConstraint<RangeRouteConstraint>(defaults, "range");
 
        // The alpha constraint uses a compiled regex which has a minimal size cost.
        AddConstraint<AlphaRouteConstraint>(defaults, "alpha");
 
#if !COMPONENTS
        AddConstraint<RegexErrorStubRouteConstraint>(defaults, "regex"); // Used to generate error message at runtime with helpful message.
        AddConstraint<RequiredRouteConstraint>(defaults, "required");
#else
        // Check if the feature is not enabled in the browser context
        if (OperatingSystem.IsBrowser() && !RegexConstraintSupport.IsEnabled)
        {
            AddConstraint<RegexErrorStubRouteConstraint>(defaults, "regex"); // Used to generate error message at runtime with helpful message.
        }
#endif
 
        // Files
        AddConstraint<FileNameRouteConstraint>(defaults, "file");
        AddConstraint<NonFileNameRouteConstraint>(defaults, "nonfile");
 
        return defaults;
    }
 
    /// <summary>
    /// Adds or overwrites the parameter policy with the associated route pattern token.
    /// </summary>
    /// <typeparam name="T">The parameter policy type.</typeparam>
    /// <param name="token">The route token used to apply the parameter policy.</param>
    public void SetParameterPolicy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string token) where T : IParameterPolicy
    {
        _constraintTypeMap[token] = typeof(T);
    }
 
    /// <summary>
    /// Adds or overwrites the parameter policy with the associated route pattern token.
    /// </summary>
    /// <param name="token">The route token used to apply the parameter policy.</param>
    /// <param name="type">The parameter policy type.</param>
    /// <exception cref="InvalidOperationException">Throws an exception if the type is not an <see cref="IParameterPolicy"/>.</exception>
    public void SetParameterPolicy(string token, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type)
    {
        if (!type.IsAssignableTo(typeof(IParameterPolicy)))
        {
            throw new InvalidOperationException($"{type} must implement {typeof(IParameterPolicy)}");
        }
 
        _constraintTypeMap[token] = type;
    }
 
    private static void AddConstraint<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TConstraint>(Dictionary<string, Type> constraintMap, string text) where TConstraint : IRouteConstraint
    {
        constraintMap[text] = typeof(TConstraint);
    }
}