Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
2737eab
Samples - WIP
annelo-msft Mar 2, 2024
e1f7a82
README updates
annelo-msft Mar 4, 2024
6bae518
Merge remote-tracking branch 'upstream/main' into clientmodel-samples
annelo-msft Mar 4, 2024
e43c767
Configuration samples
annelo-msft Mar 4, 2024
e0fee6f
updates before generating snippets
annelo-msft Mar 4, 2024
9dbf76b
update snippets
annelo-msft Mar 4, 2024
433c3fc
readme updates
annelo-msft Mar 4, 2024
a7934ed
intermediate backup
annelo-msft Mar 4, 2024
f7adcbf
updates
annelo-msft Mar 4, 2024
2d4ec41
fix
annelo-msft Mar 4, 2024
b9e406f
updates
annelo-msft Mar 4, 2024
8408cf3
nit
annelo-msft Mar 4, 2024
b316e9c
nit
annelo-msft Mar 4, 2024
f0c2877
fix links
annelo-msft Mar 5, 2024
456c6b5
updates from PR feedback
annelo-msft Mar 5, 2024
c0db649
revert engsys file
annelo-msft Mar 5, 2024
75d0161
update product
annelo-msft Mar 5, 2024
f6c136c
Merge remote-tracking branch 'upstream/main' into clientmodel-samples
annelo-msft Mar 5, 2024
262e923
add sample client implementation
annelo-msft Mar 5, 2024
552b485
add input model to sample client method
annelo-msft Mar 5, 2024
d83555e
change API key in samples
annelo-msft Mar 5, 2024
6d99f19
add inline comments to sample client and change defaults on HttpClien…
annelo-msft Mar 6, 2024
ee42d04
update impressions link
annelo-msft Mar 6, 2024
e8b6e09
restructure to address PR feedback
annelo-msft Mar 6, 2024
81eae19
nits
annelo-msft Mar 6, 2024
095324c
nits
annelo-msft Mar 6, 2024
47a47ca
nits
annelo-msft Mar 6, 2024
4ef9646
Merge remote-tracking branch 'upstream/main' into clientmodel-samples
annelo-msft Mar 6, 2024
91d3223
small updates from PR feedback
annelo-msft Mar 6, 2024
863cec6
add comment
annelo-msft Mar 7, 2024
7a02ba2
rework convenience methods section in README
annelo-msft Mar 7, 2024
c9fc7d0
more updates; add dotnet-api slug
annelo-msft Mar 7, 2024
f459730
Add sample showing response classifier
annelo-msft Mar 8, 2024
3c75fa9
updates:
annelo-msft Mar 8, 2024
8040105
reference error response configuration sample from README
annelo-msft Mar 8, 2024
24cc903
Merge remote-tracking branch 'upstream/main' into clientmodel-samples
annelo-msft Mar 8, 2024
a49b27b
update samples README
annelo-msft Mar 8, 2024
5f00c79
update md files
annelo-msft Mar 8, 2024
49896a6
show creation of BinaryContent from model in RequestOptions sample
annelo-msft Mar 8, 2024
81e2e20
add examples of different way to create BinaryContent
annelo-msft Mar 8, 2024
3aed272
show protocol method implementation and message.Apply(options)
annelo-msft Mar 8, 2024
d7a36c7
updates
annelo-msft Mar 8, 2024
6c56e7c
nits
annelo-msft Mar 8, 2024
2719c61
nits
annelo-msft Mar 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
304 changes: 261 additions & 43 deletions sdk/core/System.ClientModel/README.md

Large diffs are not rendered by default.

98 changes: 98 additions & 0 deletions sdk/core/System.ClientModel/samples/Configuration.md
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

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)
};
```
24 changes: 24 additions & 0 deletions sdk/core/System.ClientModel/samples/ModelReaderWriter.md
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));
```
14 changes: 14 additions & 0 deletions sdk/core/System.ClientModel/samples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
page_type: sample
languages:
- csharp
products:
- dotnet-core
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)
112 changes: 112 additions & 0 deletions sdk/core/System.ClientModel/samples/ServiceMethods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# System.ClientModel-based client service methods

## Introduction

`System.ClientModel`-based clients, or **service clients**, provide an interface to cloud services by translating library calls to HTTP requests.

In service clients, there are two ways to expose the schematized body in the request or response, known as the **message body**:

- **Convenience methods** take strongly-typed models as parameters. These models are C# classes which map to the message body of the REST call.

- **Protocol method** take primitive types as parameters and their `BinaryContent` input parameters mirror the message body directly. Protocol methods provide more direct access to the HTTP API protocol used by the service.

## Convenience methods

**Convenience methods** provide a convenient way to invoke a service operation. They are service methods that take a strongly-typed model representing schematized data sent to the service as input, and return a strongly-typed model representing the payload from the service response as output. Having strongly-typed models that represent service concepts provides a layer of convenience over working with the raw payload format. This is because these models unify the client user experience when cloud services differ in payload formats. That is, a client-user can learn the patterns for strongly-typed models that `System.ClientModel`-based clients provide, and use them together without having to reason about whether a cloud service represents resources using, for example, JSON or XML formats.

The following sample illustrates how to call a convenience method and access both the strongly-typed output model and the details of the HTTP response.

```C# Snippet:ClientResultTReadme
// Create a client
string? key = Environment.GetEnvironmentVariable("MAPS_API_KEY");
ApiKeyCredential credential = new(key!);
MapsClient client = new(new Uri("https://atlas.microsoft.com"), credential);

// Call a service method, which returns ClientResult<T>
IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189");
ClientResult<IPAddressCountryPair> result = await client.GetCountryCodeAsync(ipAddress);

// ClientResult<T> has two members:
//
// (1) A Value property to access the strongly-typed output
IPAddressCountryPair value = result.Value;
Console.WriteLine($"Country is {value.CountryRegion.IsoCode}.");

// (2) A GetRawResponse method for accessing the details of the HTTP response
PipelineResponse response = result.GetRawResponse();

Console.WriteLine($"Response status code: '{response.Status}'.");
Console.WriteLine("Response headers:");
foreach (KeyValuePair<string, string> header in response.Headers)
{
Console.WriteLine($"Name: '{header.Key}', Value: '{header.Value}'.");
}
```

## Protocol methods

In contrast to convenience methods, **protocol methods** are service methods that provide very little convenience over the raw HTTP APIs a cloud service exposes. They represent request and response message bodies using types that are very thin layers over raw JSON/binary/other formats. Users of client protocol methods must reference a service's API documentation directly, rather than relying on the client to provide developer conveniences via strongly-typing service schemas.

The following sample illustrates how to call a protocol method, including creating the request payload and accessing the details of the HTTP response.

```C# Snippet:ServiceMethodsProtocolMethod
// Create a BinaryData instance from a JSON string literal.
BinaryData input = BinaryData.FromString("""
{
"countryRegion": {
"isoCode": "US"
},
}
""");

// Create a BinaryContent instance to set as the HTTP request content.
BinaryContent requestContent = BinaryContent.Create(input);

// Call the protocol method
ClientResult result = await client.AddCountryCodeAsync(requestContent);

// Obtain the output response content from the returned ClientResult.
BinaryData output = result.GetRawResponse().Content;

using JsonDocument outputAsJson = JsonDocument.Parse(output.ToString());
string isoCode = outputAsJson.RootElement
.GetProperty("countryRegion")
.GetProperty("isoCode")
.GetString();

Console.WriteLine($"Code for added country is '{isoCode}'.");
```

Protocol methods take an optional `RequestOptions` parameter. `RequestOptions` can be used to modify various aspects of the HTTP request sent by the service method, such as adding a request header, or adding a policy to the client pipeline that can modify the request directly before sending it to the service. `RequestOptions` also enables passing a `CancellationToken` to the method.

```C# Snippet:RequestOptionsReadme
// Create RequestOptions instance
RequestOptions options = new();

// Set CancellationToken
options.CancellationToken = cancellationToken;

// Add a header to the request
options.AddHeader("CustomHeader", "CustomHeaderValue");

// Call protocol method to pass RequestOptions
ClientResult output = await client.GetCountryCodeAsync(ipAddress.ToString(), options);
```

## Handling exceptions

When a service call fails, service clients throw a `ClientResultException`. The exception exposes the HTTP status code and the details of the service response if available.

```C# Snippet:ClientResultExceptionReadme
try
{
IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189");
ClientResult<IPAddressCountryPair> result = await client.GetCountryCodeAsync(ipAddress);
}
// Handle exception with status code 404
catch (ClientResultException e) when (e.Status == 404)
{
// Handle not found error
Console.Error.WriteLine($"Error: Response failed with status code: '{e.Status}'");
}
```
119 changes: 119 additions & 0 deletions sdk/core/System.ClientModel/tests/Samples/ConfigurationSamples.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
using Maps;
using NUnit.Framework;

namespace System.ClientModel.Tests.Samples;

public class ConfigurationSamples
{
[Test]
[Ignore("Used for README")]
public void ClientModelConfigurationReadme()
{
#region Snippet:ClientModelConfigurationReadme

MapsClientOptions options = new()
{
NetworkTimeout = TimeSpan.FromSeconds(120),
};

string? key = Environment.GetEnvironmentVariable("MAPS_API_KEY");
ApiKeyCredential credential = new(key!);
MapsClient client = new(new Uri("https://atlas.microsoft.com"), credential, options);

#endregion
}

[Test]
[Ignore("Used for README")]
public void ConfigurationCustomizeRetries()
{
#region 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);

#endregion
}

[Test]
public void ConfigurationAddPolicies()
{
#region Snippet:ConfigurationAddPerCallPolicy
MapsClientOptions options = new();
options.AddPolicy(new StopwatchPolicy(), PipelinePosition.PerCall);
#endregion

#region Snippet:ConfigurationAddPerTryPolicy
options.AddPolicy(new StopwatchPolicy(), PipelinePosition.PerTry);
#endregion

#region Snippet:ConfigurationAddBeforeTransportPolicy
options.AddPolicy(new StopwatchPolicy(), PipelinePosition.BeforeTransport);
#endregion
}

#region 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}");
}
}
#endregion

[Test]
public void ConfigurationCustomHttpClient()
{
#region 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)
};
#endregion
}
}
Loading