| File: UrlSearchParams.cs | Web Access |
| Project: Microsoft.AspNetCore.Components.Platform.csproj (Microsoft.AspNetCore.Components.Platform) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.CompilerServices; using System.Text.Json; using Microsoft.JSInterop; namespace Microsoft.AspNetCore.Components.Platform; /// <summary> /// Represents a live browser URL query parameter collection. /// </summary> /// <remarks> /// Dispose instances created by <see cref="UrlSearchParamsConstructor"/>. Instances obtained from <see cref="Url.SearchParams"/> are owned by their URL and should not be disposed separately. /// </remarks> public sealed class UrlSearchParams : IAsyncDisposable { private Task<IJSObjectReference>? _referenceTask; private readonly IJSObjectReference? _ownerReference; private readonly string? _propertyName; private bool _disposed; internal UrlSearchParams(IJSObjectReference reference) { _referenceTask = Task.FromResult(reference); } internal UrlSearchParams(IJSObjectReference ownerReference, string propertyName) { _ownerReference = ownerReference; _propertyName = propertyName; } /// <summary> /// Gets the number of query parameters. /// </summary> /// <returns>The number of query parameters.</returns> public async ValueTask<uint> GetSizeAsync() { var reference = await GetReferenceAsync().ConfigureAwait(false); return await reference.GetValueAsync<uint>("size").ConfigureAwait(false); } /// <summary> /// Appends a query parameter. /// </summary> /// <param name="name">The query parameter name.</param> /// <param name="value">The query parameter value.</param> /// <returns>A <see cref="ValueTask"/> representing the completion of the operation.</returns> /// <exception cref="ArgumentNullException"><paramref name="name"/> or <paramref name="value"/> is <see langword="null"/>.</exception> public async ValueTask AppendAsync(string name, string value) { ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(value); var reference = await GetReferenceAsync().ConfigureAwait(false); await reference.InvokeVoidAsync("append", name, value).ConfigureAwait(false); } /// <summary> /// Deletes all query parameters with the specified name. /// </summary> /// <param name="name">The query parameter name.</param> /// <returns>A <see cref="ValueTask"/> representing the completion of the operation.</returns> /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception> public async ValueTask DeleteAsync(string name) { ArgumentNullException.ThrowIfNull(name); var reference = await GetReferenceAsync().ConfigureAwait(false); await reference.InvokeVoidAsync("delete", name).ConfigureAwait(false); } /// <summary> /// Deletes query parameters with the specified name and value. /// </summary> /// <param name="name">The query parameter name.</param> /// <param name="value">The query parameter value.</param> /// <returns>A <see cref="ValueTask"/> representing the completion of the operation.</returns> /// <exception cref="ArgumentNullException"><paramref name="name"/> or <paramref name="value"/> is <see langword="null"/>.</exception> public async ValueTask DeleteAsync(string name, string value) { ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(value); var reference = await GetReferenceAsync().ConfigureAwait(false); await reference.InvokeVoidAsync("delete", name, value).ConfigureAwait(false); } /// <summary> /// Gets the first value associated with a query parameter. /// </summary> /// <param name="name">The query parameter name.</param> /// <returns>The first value associated with <paramref name="name"/>, or <see langword="null"/> if the query parameter does not exist.</returns> /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception> public async ValueTask<string?> GetAsync(string name) { ArgumentNullException.ThrowIfNull(name); var reference = await GetReferenceAsync().ConfigureAwait(false); return await reference.InvokeAsync<string?>("get", name).ConfigureAwait(false); } /// <summary> /// Gets all values associated with a query parameter. /// </summary> /// <param name="name">The query parameter name.</param> /// <returns>All values associated with <paramref name="name"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception> public async ValueTask<IReadOnlyList<string>> GetAllAsync(string name) { ArgumentNullException.ThrowIfNull(name); var reference = await GetReferenceAsync().ConfigureAwait(false); return await reference.InvokeAsync<string[]>("getAll", name).ConfigureAwait(false); } /// <summary> /// Determines whether a query parameter with the specified name exists. /// </summary> /// <param name="name">The query parameter name.</param> /// <returns><see langword="true"/> if the query parameter exists; otherwise, <see langword="false"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception> public async ValueTask<bool> HasAsync(string name) { ArgumentNullException.ThrowIfNull(name); var reference = await GetReferenceAsync().ConfigureAwait(false); return await reference.InvokeAsync<bool>("has", name).ConfigureAwait(false); } /// <summary> /// Determines whether a query parameter with the specified name and value exists. /// </summary> /// <param name="name">The query parameter name.</param> /// <param name="value">The query parameter value.</param> /// <returns><see langword="true"/> if the query parameter exists; otherwise, <see langword="false"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="name"/> or <paramref name="value"/> is <see langword="null"/>.</exception> public async ValueTask<bool> HasAsync(string name, string value) { ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(value); var reference = await GetReferenceAsync().ConfigureAwait(false); return await reference.InvokeAsync<bool>("has", name, value).ConfigureAwait(false); } /// <summary> /// Sets a query parameter, replacing all existing values with the specified value. /// </summary> /// <param name="name">The query parameter name.</param> /// <param name="value">The query parameter value.</param> /// <returns>A <see cref="ValueTask"/> representing the completion of the operation.</returns> /// <exception cref="ArgumentNullException"><paramref name="name"/> or <paramref name="value"/> is <see langword="null"/>.</exception> public async ValueTask SetAsync(string name, string value) { ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(value); var reference = await GetReferenceAsync().ConfigureAwait(false); await reference.InvokeVoidAsync("set", name, value).ConfigureAwait(false); } /// <summary> /// Sorts query parameters by name. /// </summary> /// <returns>A <see cref="ValueTask"/> representing the completion of the operation.</returns> public async ValueTask SortAsync() { var reference = await GetReferenceAsync().ConfigureAwait(false); await reference.InvokeVoidAsync("sort").ConfigureAwait(false); } /// <summary> /// Enumerates the live query parameter names and values. /// </summary> /// <returns>An asynchronous sequence of query parameter names and values.</returns> /// <remarks> /// Each enumeration starts a new live iterator on its first advance. Keep the collection alive /// until enumeration ends. Cancellation is observed between browser operations; it does not abort an operation in progress. /// </remarks> public IAsyncEnumerable<KeyValuePair<string, string>> EntriesAsync() => EnumerateAsync("entries", static value => new KeyValuePair<string, string>(value[0].GetString()!, value[1].GetString()!)); /// <summary> /// Enumerates the live query parameter names. /// </summary> /// <returns>An asynchronous sequence of query parameter names.</returns> /// <remarks> /// Each enumeration starts a new live iterator on its first advance. Keep the collection alive /// until enumeration ends. Cancellation is observed between browser operations; it does not abort an operation in progress. /// </remarks> public IAsyncEnumerable<string> KeysAsync() => EnumerateAsync("keys", static value => value.GetString()!); /// <summary> /// Enumerates the live query parameter values. /// </summary> /// <returns>An asynchronous sequence of query parameter values.</returns> /// <remarks> /// Each enumeration starts a new live iterator on its first advance. Keep the collection alive /// until enumeration ends. Cancellation is observed between browser operations; it does not abort an operation in progress. /// </remarks> public IAsyncEnumerable<string> ValuesAsync() => EnumerateAsync("values", static value => value.GetString()!); /// <summary> /// Converts the query parameters to their serialized string form. /// </summary> /// <returns>The serialized query parameters.</returns> public async ValueTask<string> ToStringAsync() { var reference = await GetReferenceAsync().ConfigureAwait(false); return await reference.InvokeAsync<string>("toString").ConfigureAwait(false); } /// <summary> /// Releases the query parameter collection. /// </summary> /// <returns>A <see cref="ValueTask"/> representing the completion of the operation.</returns> /// <remarks> /// Do not dispose this instance if it was obtained from <see cref="Url.SearchParams"/>. The containing URL owns that instance. /// </remarks> public async ValueTask DisposeAsync() { if (_disposed) { return; } _disposed = true; if (_referenceTask is { IsCompletedSuccessfully: true } referenceTask) { await referenceTask.Result.DisposeAsync().ConfigureAwait(false); } } private async IAsyncEnumerable<T> EnumerateAsync<T>( string identifier, Func<JsonElement, T> convertValue, [EnumeratorCancellation] CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); var reference = await GetReferenceAsync().ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); var iterator = await reference.InvokeAsync<IJSObjectReference>(identifier).ConfigureAwait(false); await using (iterator.ConfigureAwait(false)) { while (true) { cancellationToken.ThrowIfCancellationRequested(); ObjectDisposedException.ThrowIf(_disposed, this); var result = await iterator.InvokeAsync<IteratorResult>("next").ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); ObjectDisposedException.ThrowIf(_disposed, this); if (result.Done) { yield break; } yield return convertValue(result.Value); } } } private sealed class IteratorResult { public bool Done { get; init; } public JsonElement Value { get; init; } } private Task<IJSObjectReference> GetReferenceAsync() { ObjectDisposedException.ThrowIf(_disposed, this); if (_referenceTask is null || _referenceTask.IsFaulted || _referenceTask.IsCanceled) { _referenceTask = _ownerReference!.GetValueAsync<IJSObjectReference>(_propertyName!).AsTask(); } return _referenceTask; } }