mirror of
https://github.com/dotnet/skills.git
synced 2026-09-20 09:49:54 +08:00
042a9d2b2c
* Reorganize plugins per agreed structure (#271) Split the dotnet plugin into domain-specific plugins per discussion in #271: - dotnet: narrowed to common everyday C#/.NET coding tasks - dotnet-diag: perf investigations, debugging, incident analysis - dotnet-data: data access and Entity Framework - dotnet-aspnet, dotnet-wpf, dotnet-winforms, dotnet-maui: scaffolded empty to convey intent for future framework-specific skills Moved skills, tests, and CODEOWNERS entries accordingly. No file content was changed; all skill/test moves are pure renames. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Improve dotnet-diag and dotnet-winforms descriptions - dotnet-diag: add '.NET' qualifier to clarify scope - dotnet-winforms: use official 'Windows Forms (WinForms)' name Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add .NET qualifier to dotnet-data description Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove empty plugins; list them in CONTRIBUTING.md as future options Remove the four empty placeholder plugins (dotnet-aspnet, dotnet-wpf, dotnet-winforms, dotnet-maui) that had only plugin.json and no skills. Add a 'Plugin organization' section to CONTRIBUTING.md that describes the domain-specific plugin groupings introduced in this PR and lists the four removed plugin names as reserved candidates for future contributions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Create dotnet-upgrade plugin with migration/upgrade skills Move migrate-nullable-references, thread-abort-migration, and dotnet-aot-compat from dotnet to the new dotnet-upgrade plugin. Update CODEOWNERS paths (same owners), CONTRIBUTING.md plugin table, and fix the thread-abort-migration SKILL.md link in CONTRIBUTING.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
264 lines
14 KiB
C#
264 lines
14 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Azure.Core.Pipeline;
|
|
|
|
#nullable enable
|
|
|
|
namespace Azure.Core
|
|
{
|
|
/// <summary>
|
|
/// A helper class used to build long-running operation instances. In order to use this helper:
|
|
/// <list type="number">
|
|
/// <item>Make sure your LRO implements the <see cref="IOperation"/> interface.</item>
|
|
/// <item>Add a private <see cref="OperationInternal"/> field to your LRO, and instantiate it during construction.</item>
|
|
/// <item>Delegate method calls to the <see cref="OperationInternal"/> implementations.</item>
|
|
/// </list>
|
|
/// Supported members:
|
|
/// <list type="bullet">
|
|
/// <item>
|
|
/// <description><see cref="OperationInternalBase.HasCompleted"/></description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description><see cref="OperationInternalBase.RawResponse"/>, used for <see cref="Operation.GetRawResponse"/></description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description><see cref="OperationInternalBase.UpdateStatus"/></description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description><see cref="OperationInternalBase.UpdateStatusAsync(CancellationToken)"/></description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description><see cref="OperationInternalBase.WaitForCompletionResponseAsync(CancellationToken)"/></description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description><see cref="OperationInternalBase.WaitForCompletionResponseAsync(TimeSpan, CancellationToken)"/></description>
|
|
/// </item>
|
|
/// </list>
|
|
/// </summary>
|
|
internal class OperationInternal : OperationInternalBase
|
|
{
|
|
// To minimize code duplication and avoid introduction of another type,
|
|
// OperationInternal delegates implementation to the OperationInternal<VoidValue>.
|
|
// VoidValue is a private empty struct which only purpose is to be used as generic parameter.
|
|
private readonly OperationInternal<VoidValue> _internalOperation;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="OperationInternal"/> class in a final successful state.
|
|
/// </summary>
|
|
/// <param name="rawResponse">The final value of <see cref="OperationInternalBase.RawResponse"/>.</param>
|
|
public static OperationInternal Succeeded(Response rawResponse) => new(OperationState.Success(rawResponse));
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="OperationInternal"/> class in a final failed state.
|
|
/// </summary>
|
|
/// <param name="rawResponse">The final value of <see cref="OperationInternalBase.RawResponse"/>.</param>
|
|
/// <param name="operationFailedException">The exception that will be thrown by <c>UpdateStatusAsync</c>.</param>
|
|
public static OperationInternal Failed(Response rawResponse, RequestFailedException operationFailedException) => new(OperationState.Failure(rawResponse, operationFailedException));
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="OperationInternal"/> class.
|
|
/// </summary>
|
|
/// <param name="operation">The long-running operation making use of this class. Passing "<c>this</c>" is expected.</param>
|
|
/// <param name="clientDiagnostics">Used for diagnostic scope and exception creation. This is expected to be the instance created during the construction of your main client.</param>
|
|
/// <param name="rawResponse">
|
|
/// The initial value of <see cref="OperationInternalBase.RawResponse"/>. Usually, long-running operation objects can be instantiated in two ways:
|
|
/// <list type="bullet">
|
|
/// <item>
|
|
/// When calling a client's "<c>Start<OperationName></c>" method, a service call is made to start the operation, and an <see cref="Operation"/> instance is returned.
|
|
/// In this case, the response received from this service call can be passed here.
|
|
/// </item>
|
|
/// <item>
|
|
/// When a user instantiates an <see cref="Operation"/> directly using a public constructor, there's no previous service call. In this case, passing <c>null</c> is expected.
|
|
/// </item>
|
|
/// </list>
|
|
/// </param>
|
|
/// <param name="operationTypeName">
|
|
/// The type name of the long-running operation making use of this class. Used when creating diagnostic scopes. If left <c>null</c>, the type name will be inferred based on the
|
|
/// parameter <paramref name="operation"/>.
|
|
/// </param>
|
|
/// <param name="scopeAttributes">The attributes to use during diagnostic scope creation.</param>
|
|
/// <param name="fallbackStrategy"> The delay strategy to use. Default is <see cref="FixedDelayWithNoJitterStrategy"/>.</param>
|
|
public OperationInternal(IOperation operation,
|
|
ClientDiagnostics clientDiagnostics,
|
|
Response rawResponse,
|
|
string? operationTypeName = null,
|
|
IEnumerable<KeyValuePair<string, string>>? scopeAttributes = null,
|
|
DelayStrategy? fallbackStrategy = null)
|
|
: base(clientDiagnostics, operationTypeName ?? operation.GetType().Name, scopeAttributes, fallbackStrategy)
|
|
{
|
|
_internalOperation = new OperationInternal<VoidValue>(new OperationToOperationOfTProxy(operation), clientDiagnostics, rawResponse, operationTypeName ?? operation.GetType().Name, scopeAttributes, fallbackStrategy);
|
|
}
|
|
|
|
internal OperationInternal(OperationState finalState)
|
|
: base(finalState.RawResponse)
|
|
{
|
|
_internalOperation = finalState.HasSucceeded
|
|
? OperationInternal<VoidValue>.Succeeded(finalState.RawResponse, default)
|
|
: OperationInternal<VoidValue>.Failed(finalState.RawResponse, finalState.OperationFailedException!);
|
|
}
|
|
|
|
public override Response RawResponse => _internalOperation.RawResponse;
|
|
|
|
public override bool HasCompleted => _internalOperation.HasCompleted;
|
|
|
|
protected override async ValueTask<Response> UpdateStatusAsync(bool async, CancellationToken cancellationToken) =>
|
|
async ? await _internalOperation.UpdateStatusAsync(cancellationToken).ConfigureAwait(false) : _internalOperation.UpdateStatus(cancellationToken);
|
|
|
|
// Wrapper type that converts OperationState to OperationState<T> and can be passed to `OperationInternal<T>` constructor.
|
|
private class OperationToOperationOfTProxy : IOperation<VoidValue>
|
|
{
|
|
private readonly IOperation _operation;
|
|
|
|
public OperationToOperationOfTProxy(IOperation operation)
|
|
{
|
|
_operation = operation;
|
|
}
|
|
|
|
public RehydrationToken GetRehydrationToken() => _operation.GetRehydrationToken();
|
|
|
|
public async ValueTask<OperationState<VoidValue>> UpdateStateAsync(bool async, CancellationToken cancellationToken)
|
|
{
|
|
var state = await _operation.UpdateStateAsync(async, cancellationToken).ConfigureAwait(false);
|
|
if (!state.HasCompleted)
|
|
{
|
|
return OperationState<VoidValue>.Pending(state.RawResponse);
|
|
}
|
|
|
|
if (state.HasSucceeded)
|
|
{
|
|
return OperationState<VoidValue>.Success(state.RawResponse, new VoidValue());
|
|
}
|
|
|
|
return OperationState<VoidValue>.Failure(state.RawResponse, state.OperationFailedException);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// An interface used by <see cref="OperationInternal"/> for making service calls and updating state. It's expected that
|
|
/// your long-running operation classes implement this interface.
|
|
/// </summary>
|
|
internal interface IOperation
|
|
{
|
|
/// <summary>
|
|
/// Calls the service and updates the state of the long-running operation. Properties directly handled by the
|
|
/// <see cref="OperationInternal"/> class, such as <see cref="OperationInternalBase.RawResponse"/>
|
|
/// don't need to be updated. Operation-specific properties, such as "<c>CreateOn</c>" or "<c>LastModified</c>",
|
|
/// must be manually updated by the operation implementing this method.
|
|
/// <example>Usage example:
|
|
/// <code>
|
|
/// async ValueTask<OperationState> IOperation.UpdateStateAsync(bool async, CancellationToken cancellationToken)<br/>
|
|
/// {<br/>
|
|
/// Response<R> response = async ? <async service call> : <sync service call>;<br/>
|
|
/// if (<operation succeeded>) return OperationState.Success(response.GetRawResponse(), <parse response>);<br/>
|
|
/// if (<operation failed>) return OperationState.Failure(response.GetRawResponse());<br/>
|
|
/// return OperationState.Pending(response.GetRawResponse());<br/>
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
/// </summary>
|
|
/// <param name="async"><c>true</c> if the call should be executed asynchronously. Otherwise, <c>false</c>.</param>
|
|
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
|
|
/// <returns>
|
|
/// A structure indicating the current operation state. The <see cref="OperationState"/> structure must be instantiated by one of
|
|
/// its static methods:
|
|
/// <list type="bullet">
|
|
/// <item>Use <see cref="OperationState.Success"/> when the operation has completed successfully.</item>
|
|
/// <item>Use <see cref="OperationState.Failure"/> when the operation has completed with failures.</item>
|
|
/// <item>Use <see cref="OperationState.Pending"/> when the operation has not completed yet.</item>
|
|
/// </list>
|
|
/// </returns>
|
|
ValueTask<OperationState> UpdateStateAsync(bool async, CancellationToken cancellationToken);
|
|
|
|
/// <summary>
|
|
/// Get a token that can be used to rehydrate the operation.
|
|
/// </summary>
|
|
RehydrationToken GetRehydrationToken();
|
|
}
|
|
|
|
/// <summary>
|
|
/// A helper structure passed to <see cref="OperationInternal"/> to indicate the current operation state. This structure must be
|
|
/// instantiated by one of its static methods, depending on the operation state:
|
|
/// <list type="bullet">
|
|
/// <item>Use <see cref="OperationState.Success"/> when the operation has completed successfully.</item>
|
|
/// <item>Use <see cref="OperationState.Failure"/> when the operation has completed with failures.</item>
|
|
/// <item>Use <see cref="OperationState.Pending"/> when the operation has not completed yet.</item>
|
|
/// </list>
|
|
/// </summary>
|
|
internal readonly struct OperationState
|
|
{
|
|
private OperationState(Response rawResponse, bool hasCompleted, bool hasSucceeded, RequestFailedException? operationFailedException)
|
|
{
|
|
RawResponse = rawResponse;
|
|
HasCompleted = hasCompleted;
|
|
HasSucceeded = hasSucceeded;
|
|
OperationFailedException = operationFailedException;
|
|
}
|
|
|
|
public Response RawResponse { get; }
|
|
|
|
public bool HasCompleted { get; }
|
|
|
|
public bool HasSucceeded { get; }
|
|
|
|
public RequestFailedException? OperationFailedException { get; }
|
|
|
|
/// <summary>
|
|
/// Instantiates an <see cref="OperationState"/> indicating the operation has completed successfully.
|
|
/// </summary>
|
|
/// <param name="rawResponse">The HTTP response obtained during the status update.</param>
|
|
/// <returns>A new <see cref="OperationState"/> instance.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown if <paramref name="rawResponse"/> is <c>null</c>.</exception>
|
|
public static OperationState Success(Response rawResponse)
|
|
{
|
|
if (rawResponse is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(rawResponse));
|
|
}
|
|
|
|
return new OperationState(rawResponse, true, true, default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Instantiates an <see cref="OperationState"/> indicating the operation has completed with failures.
|
|
/// </summary>
|
|
/// <param name="rawResponse">The HTTP response obtained during the status update.</param>
|
|
/// <param name="operationFailedException">
|
|
/// The exception to throw from <c>UpdateStatus</c> because of the operation failure. If left <c>null</c>,
|
|
/// a default exception is created based on the <paramref name="rawResponse"/> parameter.
|
|
/// </param>
|
|
/// <returns>A new <see cref="OperationState"/> instance.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown if <paramref name="rawResponse"/> is <c>null</c>.</exception>
|
|
public static OperationState Failure(Response rawResponse, RequestFailedException? operationFailedException = null)
|
|
{
|
|
if (rawResponse is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(rawResponse));
|
|
}
|
|
|
|
return new OperationState(rawResponse, true, false, operationFailedException);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Instantiates an <see cref="OperationState"/> indicating the operation has not completed yet.
|
|
/// </summary>
|
|
/// <param name="rawResponse">The HTTP response obtained during the status update.</param>
|
|
/// <returns>A new <see cref="OperationState"/> instance.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown if <paramref name="rawResponse"/> is <c>null</c>.</exception>
|
|
public static OperationState Pending(Response rawResponse)
|
|
{
|
|
if (rawResponse is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(rawResponse));
|
|
}
|
|
|
|
return new OperationState(rawResponse, false, default, default);
|
|
}
|
|
}
|
|
}
|