File: Models\InteractiveRequestOptions.cs
Web Access
Project: src\aspnetcore\src\Components\WebAssembly\WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj (Microsoft.AspNetCore.Components.WebAssembly.Authentication)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
using static Microsoft.AspNetCore.Internal.LinkerFlags;
 
namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication;
 
/// <summary>
/// Represents the request to the identity provider for logging in or provisioning a token.
/// </summary>
[JsonConverter(typeof(Converter))]
public sealed class InteractiveRequestOptions
{
    /// <summary>
    /// Gets the request type.
    /// </summary>
    public required InteractionType Interaction { get; init; }
 
    /// <summary>
    /// Gets the redirect URL this request must return to upon successful completion.
    /// </summary>
    public required string ReturnUrl { get; init; }
 
    /// <summary>
    /// Gets the scopes this request must use in the operation.
    /// </summary>
    public IEnumerable<string> Scopes { get; init; } = Array.Empty<string>();
 
    private Dictionary<string, object>? AdditionalRequestParameters { get; set; }
 
    /// <summary>
    /// Tries to add an additional parameter to pass in to the underlying provider.
    /// </summary>
    /// <remarks>
    /// The underlying provider is free to apply these parameters as it sees fit or ignore them completely. In the default
    /// implementations the parameters will be JSON serialized using System.Text.Json and passed as a parameter to the
    /// underlying JavaScript implementation that handles the operation details.
    /// </remarks>
    public bool TryAddAdditionalParameter<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string name, TValue value)
    {
        ArgumentNullException.ThrowIfNull(name, nameof(name));
        AdditionalRequestParameters ??= new();
        return AdditionalRequestParameters.TryAdd(name, value!);
    }
 
    /// <summary>
    /// Tries to remove an existing additional parameter.
    /// </summary>
    public bool TryRemoveAdditionalParameter(string name)
    {
        ArgumentNullException.ThrowIfNull(name, nameof(name));
        return AdditionalRequestParameters != null && AdditionalRequestParameters.Remove(name);
    }
 
    /// <summary>
    /// Tries to retrieve an existing additional parameter.
    /// </summary>
    public bool TryGetAdditionalParameter<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string name, [NotNullWhen(true)] out TValue? value)
    {
        ArgumentNullException.ThrowIfNull(name);
 
        value = default;
        if (AdditionalRequestParameters == null || !AdditionalRequestParameters.TryGetValue(name, out var rawValue))
        {
            return false;
        }
        if (rawValue is JsonElement json)
        {
            value = Deserialize(json)!;
            AdditionalRequestParameters[name] = value;
            return true;
        }
        else
        {
            value = (TValue)rawValue;
            return true;
        }
 
        [UnconditionalSuppressMessage(
            "Trimming",
            "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
            Justification = "The types this method deserializes are annotated with 'DynamicallyAccessedMembers' to prevent them from being linked out as part of 'TryAddAdditionalParameter'.")]
        static TValue Deserialize(JsonElement element) => element.Deserialize<TValue>()!;
    }
 
    internal string ToState() => JsonSerializer.Serialize(this, InteractiveRequestOptionsSerializerContext.Default.InteractiveRequestOptions);
 
    internal static InteractiveRequestOptions FromState(string state) => JsonSerializer.Deserialize(
        state,
        InteractiveRequestOptionsSerializerContext.Default.InteractiveRequestOptions)!;
 
    internal class Converter : JsonConverter<InteractiveRequestOptions>
    {
        public override InteractiveRequestOptions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var requestOptions = JsonSerializer.Deserialize(ref reader, InteractiveRequestOptionsSerializerContext.Default.OptionsRecord);
 
            return new InteractiveRequestOptions
            {
                AdditionalRequestParameters = requestOptions.AdditionalRequestParameters,
                Interaction = requestOptions.Interaction,
                ReturnUrl = requestOptions.ReturnUrl,
                Scopes = requestOptions.Scopes,
            };
        }
 
        public override void Write(Utf8JsonWriter writer, InteractiveRequestOptions value, JsonSerializerOptions options)
        {
            JsonSerializer.Serialize(
                writer,
                new OptionsRecord(value.ReturnUrl, value.Scopes, value.Interaction, value.AdditionalRequestParameters),
                InteractiveRequestOptionsSerializerContext.Default.OptionsRecord);
        }
 
        internal readonly struct OptionsRecord
        {
            [JsonInclude]
            public string ReturnUrl { get; init; }
 
            [JsonInclude]
            public IEnumerable<string> Scopes { get; init; }
 
            [JsonInclude]
            public InteractionType Interaction { get; init; }
 
            [JsonInclude]
            public Dictionary<string, object>? AdditionalRequestParameters { get; init; }
 
            public OptionsRecord(
                string returnUrl,
                IEnumerable<string> scopes,
                InteractionType interaction,
                Dictionary<string, object>? additionalRequestParameters)
            {
                ReturnUrl = returnUrl;
                Scopes = scopes;
                Interaction = interaction;
                AdditionalRequestParameters = additionalRequestParameters;
            }
        }
    }
}
 
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, WriteIndented = false)]
[JsonSerializable(typeof(InteractiveRequestOptions))]
[JsonSerializable(typeof(InteractiveRequestOptions.Converter.OptionsRecord))]
[JsonSerializable(typeof(JsonElement))]
internal partial class InteractiveRequestOptionsSerializerContext : JsonSerializerContext
{
}