-
Notifications
You must be signed in to change notification settings - Fork 5.1k
ClientModel: Add samples for System.ClientModel library #42369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 34 commits
Commits
Show all changes
44 commits
Select commit
Hold shift + click to select a range
2737eab
Samples - WIP
annelo-msft e1f7a82
README updates
annelo-msft 6bae518
Merge remote-tracking branch 'upstream/main' into clientmodel-samples
annelo-msft e43c767
Configuration samples
annelo-msft e0fee6f
updates before generating snippets
annelo-msft 9dbf76b
update snippets
annelo-msft 433c3fc
readme updates
annelo-msft a7934ed
intermediate backup
annelo-msft f7adcbf
updates
annelo-msft 2d4ec41
fix
annelo-msft b9e406f
updates
annelo-msft 8408cf3
nit
annelo-msft b316e9c
nit
annelo-msft f0c2877
fix links
annelo-msft 456c6b5
updates from PR feedback
annelo-msft c0db649
revert engsys file
annelo-msft 75d0161
update product
annelo-msft f6c136c
Merge remote-tracking branch 'upstream/main' into clientmodel-samples
annelo-msft 262e923
add sample client implementation
annelo-msft 552b485
add input model to sample client method
annelo-msft d83555e
change API key in samples
annelo-msft 6d99f19
add inline comments to sample client and change defaults on HttpClien…
annelo-msft ee42d04
update impressions link
annelo-msft e8b6e09
restructure to address PR feedback
annelo-msft 81eae19
nits
annelo-msft 095324c
nits
annelo-msft 47a47ca
nits
annelo-msft 4ef9646
Merge remote-tracking branch 'upstream/main' into clientmodel-samples
annelo-msft 91d3223
small updates from PR feedback
annelo-msft 863cec6
add comment
annelo-msft 7a02ba2
rework convenience methods section in README
annelo-msft c9fc7d0
more updates; add dotnet-api slug
annelo-msft f459730
Add sample showing response classifier
annelo-msft 3c75fa9
updates:
annelo-msft 8040105
reference error response configuration sample from README
annelo-msft 24cc903
Merge remote-tracking branch 'upstream/main' into clientmodel-samples
annelo-msft a49b27b
update samples README
annelo-msft 5f00c79
update md files
annelo-msft 49896a6
show creation of BinaryContent from model in RequestOptions sample
annelo-msft 81e2e20
add examples of different way to create BinaryContent
annelo-msft 3aed272
show protocol method implementation and message.Apply(options)
annelo-msft d7a36c7
updates
annelo-msft 6c56e7c
nits
annelo-msft 2719c61
nits
annelo-msft File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
149 changes: 149 additions & 0 deletions
149
sdk/core/System.ClientModel/samples/ClientImplementation.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| # System.ClientModel-based client implementation samples | ||
|
|
||
| ## Introduction | ||
|
|
||
| `System.ClientModel`-based clients, or **service clients**, are built using types provided in the `System.ClientModel` library. | ||
|
|
||
| ## Basic client implementation | ||
|
|
||
| The following sample shows a minimal example of what a service client implementation might look like. | ||
|
|
||
| ```C# Snippet:ReadmeSampleClient | ||
| public class SampleClient | ||
| { | ||
| private readonly Uri _endpoint; | ||
| private readonly ApiKeyCredential _credential; | ||
| private readonly ClientPipeline _pipeline; | ||
|
|
||
| // Constructor takes service endpoint, credential used to authenticate | ||
| // with the service, and options for configuring the client pipeline. | ||
| public SampleClient(Uri endpoint, ApiKeyCredential credential, SampleClientOptions? options = default) | ||
| { | ||
| // Default options are used if none are passed by the client's user. | ||
| options ??= new SampleClientOptions(); | ||
|
|
||
| _endpoint = endpoint; | ||
| _credential = credential; | ||
|
|
||
| // Authentication policy instance is created from the user-provided | ||
| // credential and service authentication scheme. | ||
| ApiKeyAuthenticationPolicy authenticationPolicy = ApiKeyAuthenticationPolicy.CreateBearerAuthorizationPolicy(credential); | ||
|
|
||
| // Pipeline is created from user-provided options and policies | ||
| // specific to the service client implementation. | ||
| _pipeline = ClientPipeline.Create(options, | ||
| perCallPolicies: ReadOnlySpan<PipelinePolicy>.Empty, | ||
| perTryPolicies: new PipelinePolicy[] { authenticationPolicy }, | ||
| beforeTransportPolicies: ReadOnlySpan<PipelinePolicy>.Empty); | ||
| } | ||
|
|
||
| // Service method takes an input model representing a service resource | ||
| // and returns `ClientResult<T>` holding an output model representing | ||
| // the value returned in the service response. | ||
| public ClientResult<SampleResource> UpdateResource(SampleResource resource) | ||
| { | ||
| // Create a message that can be sent via the client pipeline. | ||
| PipelineMessage message = _pipeline.CreateMessage(); | ||
|
|
||
| // Modify the request as needed to invoke the service operation. | ||
| PipelineRequest request = message.Request; | ||
| request.Method = "PATCH"; | ||
| request.Uri = new Uri($"https://www.example.com/update?id={resource.Id}"); | ||
| request.Headers.Add("Accept", "application/json"); | ||
|
|
||
| // Add request body content that will be written using methods | ||
| // defined by the model's implementation of the IJsonModel<T> interface. | ||
| request.Content = BinaryContent.Create(resource); | ||
|
|
||
| // Send the message. | ||
| _pipeline.Send(message); | ||
|
|
||
| // Obtain the response from the message Response property. | ||
| // The PipelineTransport ensures that the Response value is set | ||
| // so that every policy in the pipeline can access the property. | ||
| PipelineResponse response = message.Response!; | ||
|
|
||
| // If the response is considered an error response, throw an | ||
| // exception that exposes the response details. | ||
| if (response.IsError) | ||
| { | ||
| throw new ClientResultException(response); | ||
| } | ||
|
|
||
| // Read the content from the response body and create an instance of | ||
| // a model from it, to include in the type returned by this method. | ||
| SampleResource updated = ModelReaderWriter.Read<SampleResource>(response.Content)!; | ||
|
|
||
| // Return a ClientResult holding the model instance and the HTTP | ||
| // response details. | ||
| return ClientResult.FromValue(updated, response); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Reading and writing model content to HTTP messages | ||
|
|
||
| Service clients provide **model types** representing service resources as input parameters and return values from service clients' [convenience methods](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/System.ClientModel/samples/ServiceMethods.md#convenience-methods). Client authors can implement the `IPersistableModel<T>` and `IJsonModel<T>` interfaces their in model implementations to make it easy for clients to write input model content to request message bodies, and to read response content and create instances of output models from it. An example of how clients' service methods might use such models is shown in [Basic client implementation](#basic-client-implementation). The following sample shows a minimal example of what a persistable model implementation might look like. | ||
|
|
||
| ```C# Snippet:ReadmeSampleModel | ||
| public class SampleResource : IJsonModel<SampleResource> | ||
| { | ||
| public SampleResource(string id) | ||
| { | ||
| Id = id; | ||
| } | ||
|
|
||
| public string Id { get; init; } | ||
|
|
||
| SampleResource IJsonModel<SampleResource>.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) | ||
| => FromJson(reader); | ||
|
|
||
| SampleResource IPersistableModel<SampleResource>.Create(BinaryData data, ModelReaderWriterOptions options) | ||
| => FromJson(new Utf8JsonReader(data)); | ||
|
|
||
| string IPersistableModel<SampleResource>.GetFormatFromOptions(ModelReaderWriterOptions options) | ||
| => options.Format; | ||
|
|
||
| void IJsonModel<SampleResource>.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) | ||
| => ToJson(writer); | ||
|
|
||
| BinaryData IPersistableModel<SampleResource>.Write(ModelReaderWriterOptions options) | ||
| => ModelReaderWriter.Write(this, options); | ||
|
|
||
| // Write the model JSON that will populate the HTTP request content. | ||
| private void ToJson(Utf8JsonWriter writer) | ||
| { | ||
| writer.WriteStartObject(); | ||
| writer.WritePropertyName("id"); | ||
| writer.WriteStringValue(Id); | ||
| writer.WriteEndObject(); | ||
| } | ||
|
|
||
| // Read the JSON response content and create a model instance from it. | ||
| private static SampleResource FromJson(Utf8JsonReader reader) | ||
| { | ||
| reader.Read(); // start object | ||
| reader.Read(); // property name | ||
| reader.Read(); // id value | ||
|
|
||
| return new SampleResource(reader.GetString()!); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Configuring error response classification | ||
|
|
||
| When a client sends a request to a service, the service may respond with a success response or an error response. The `PipelineTransport` used by the client's `ClientPipeline` sets the `IsError` property on the response to indicate to the client which category the response falls in. Service method implementations are expected to check the value of `response.IsError` and throw a `ClientResultException` when it is `true`, as shown in [Basic client implementation](#basic-client-implementation). | ||
|
|
||
| To classify the response, the transport uses the `PipelineMessageClassifier` value on the `PipelineMessage.ResponseClassifier` property. By default, the transport sets `IsError` to `true` for responses with an `4xx` or `5xx` HTTP status code. Clients can override the default behavior by setting the message classifier before the request is sent. Typically, a client creates a classifier that sets `response.IsError` to `false` for only response codes that are listed as success codes for the operation in the service's API definition. This type of status code-based classifier can be created using the `PipelineMessageClassifier.Create` factory method and passing the list of success status codes, as shown in the sample below. | ||
|
|
||
| ```C# Snippet:ClientStatusCodeClassifier | ||
| // Create a message that can be sent via the client pipeline. | ||
| PipelineMessage message = _pipeline.CreateMessage(); | ||
|
|
||
| // Set a classifier that will categorize only responses with status codes | ||
| // indicating success for the service operation as non-error responses. | ||
| message.ResponseClassifier = PipelineMessageClassifier.Create(stackalloc ushort[] { 200, 202 }); | ||
| ``` | ||
|
|
||
| Client authors can also create custom classifiers derived from `PipelineMessageClassifier`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| # System.ClientModel-based client configuration samples | ||
|
|
||
| ## Configuring retries | ||
|
|
||
| To modify the retry policy, create a new instance of `ClientRetryPolicy` and set it on the `ClientPipelineOptions` passed to the client constructor. | ||
|
|
||
| By default, clients will retry a request three times using an exponential retry strategy with an initial delay of 0.8 seconds and a maximum delay of one minute. | ||
|
|
||
| ```C# Snippet:ConfigurationCustomizeRetries | ||
| MapsClientOptions options = new() | ||
| { | ||
| RetryPolicy = new ClientRetryPolicy(maxRetries: 5), | ||
| }; | ||
|
|
||
| string? key = Environment.GetEnvironmentVariable("MAPS_API_KEY"); | ||
| ApiKeyCredential credential = new(key!); | ||
| MapsClient client = new(new Uri("https://atlas.microsoft.com"), credential, options); | ||
| ``` | ||
|
|
||
| ## Implement a custom policy | ||
annelo-msft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| To implement a custom policy that can be added to the client's pipeline, create a class that derives from `PipelinePolicy` and overide its `ProcessAsync` and `Process` methods. The request can be accessed via `message.Request`. The response is accessible via `message.Response`, but will have a value only after `ProcessNextAsync`/`ProcessNext` has been called. | ||
|
|
||
| ```C# Snippet:ConfigurationCustomPolicy | ||
| public class StopwatchPolicy : PipelinePolicy | ||
| { | ||
| public override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex) | ||
| { | ||
| Stopwatch stopwatch = new(); | ||
| stopwatch.Start(); | ||
|
|
||
| await ProcessNextAsync(message, pipeline, currentIndex); | ||
|
|
||
| stopwatch.Stop(); | ||
|
|
||
| Console.WriteLine($"Request to {message.Request.Uri} took {stopwatch.Elapsed}"); | ||
| } | ||
|
|
||
| public override void Process(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex) | ||
| { | ||
| Stopwatch stopwatch = new(); | ||
| stopwatch.Start(); | ||
|
|
||
| ProcessNext(message, pipeline, currentIndex); | ||
|
|
||
| stopwatch.Stop(); | ||
|
|
||
| Console.WriteLine($"Request to {message.Request.Uri} took {stopwatch.Elapsed}"); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Add a custom policy to the pipeline | ||
|
|
||
| Azure SDKs provides a way to add policies to the pipeline at three positions, `PerCall`, `PerTry`, and `BeforeTransport`. | ||
|
|
||
| - `PerCall` policies run once per request | ||
|
|
||
| ```C# Snippet:ConfigurationAddPerCallPolicy | ||
| MapsClientOptions options = new(); | ||
| options.AddPolicy(new StopwatchPolicy(), PipelinePosition.PerCall); | ||
| ``` | ||
|
|
||
| - `PerTry` policies run each time a request is tried | ||
|
|
||
| ```C# Snippet:ConfigurationAddPerTryPolicy | ||
| options.AddPolicy(new StopwatchPolicy(), PipelinePosition.PerTry); | ||
| ``` | ||
|
|
||
| - `BeforeTransport` policies run after all other policies in the pipeline and before the request is sent by the transport. | ||
|
|
||
| Adding policies at the `BeforeTransport` position should be done with care since changes made to the request by a before-transport policy will not be visible to any logging policies that come before it in the pipeline. | ||
|
|
||
| ```C# Snippet:ConfigurationAddBeforeTransportPolicy | ||
| options.AddPolicy(new StopwatchPolicy(), PipelinePosition.BeforeTransport); | ||
| ``` | ||
|
|
||
| ## Provide a custom HttpClient instance | ||
|
|
||
| In some cases, users may want to provide a custom instance of the `HttpClient` used by a client's transport to send and receive HTTP messages. To provide a custom `HttpClient`, create a new instance of `HttpClientPipelineTransport` and pass the custom `HttpClient` instance to its constructor. | ||
|
|
||
| ```C# Snippet:ConfigurationCustomHttpClient | ||
| using HttpClientHandler handler = new() | ||
| { | ||
| // Reduce the max connections per server, which defaults to 50. | ||
| MaxConnectionsPerServer = 25, | ||
|
|
||
| // Preserve default System.ClientModel redirect behavior. | ||
| AllowAutoRedirect = false, | ||
| }; | ||
|
|
||
| using HttpClient httpClient = new(handler); | ||
|
|
||
| MapsClientOptions options = new() | ||
| { | ||
| Transport = new HttpClientPipelineTransport(httpClient) | ||
| }; | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
|
|
||
| # System.ClientModel-based ModelReaderWriter samples | ||
|
|
||
| ## Read and write persistable models | ||
|
|
||
| Client library authors can implement the `IPersistableModel<T>` or `IJsonModel<T>` interfaces on strongly-typed model implementations. If they do, end-users of service clients can then read and write those models in cases where they need to persist them to a backing store. | ||
|
|
||
| The example below shows how to write a persistable model to `BinaryData`. | ||
|
|
||
| ```C# Snippet:Readme_Write_Simple | ||
| InputModel model = new InputModel(); | ||
| BinaryData data = ModelReaderWriter.Write(model); | ||
| ``` | ||
|
|
||
| The example below shows how to read JSON to create a strongly-typed model instance. | ||
|
|
||
| ```C# Snippet:Readme_Read_Simple | ||
| string json = @"{ | ||
| ""x"": 1, | ||
| ""y"": 2, | ||
| ""z"": 3 | ||
| }"; | ||
| OutputModel? model = ModelReaderWriter.Read<OutputModel>(BinaryData.FromString(json)); | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| --- | ||
| page_type: sample | ||
| languages: | ||
| - csharp | ||
| products: | ||
| - dotnet-api | ||
| name: System.ClientModel samples for .NET | ||
| description: Samples for the System.ClientModel library | ||
| --- | ||
|
|
||
| # System.ClientModel Samples | ||
|
|
||
| - [Client Configuration](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/System.ClientModel/samples/Configuration.md) | ||
| - [Service Methods](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/System.ClientModel/samples/ServiceMethods.md) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.