Skip to content
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

feat: Adding support for custom endpointoptions #1591

Merged
merged 5 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,4 @@ ASALocalRun/

.vscode/**/*.*
DebugPlatforms.props
.DS_Store
nickrandolph marked this conversation as resolved.
Show resolved Hide resolved
221 changes: 221 additions & 0 deletions doc/Learn/Tutorials/Http/HowTo-EndpointOptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
---
uid: Learn.Tutorials.Http.HowToEndpointOptions
---

# How-To: Configure `HttpClient` with Custom Endpoint Options

It's often necessary to include an API key alongside requests to a web API. This can be done by adding a header to the request. The steps below will show you how to easily specify custom options, such as an access token, when adding an endpoint. You can then configure the associated `HttpClient` from these options.

## Pre-requisites

* An [environment](xref:Uno.GetStarted) set up for developing Uno Platform applications

* The solution was created using the template wizard or `dotnet new unoapp`. See [Overview](xref:Overview.Extensions)

* Basic conceptual understanding of accessing web resources using HTTP requests

* Knowledge of how to [register an endpoint for HTTP requests](xref:Learn.Tutorials.Http.HowToHttp)
nickrandolph marked this conversation as resolved.
Show resolved Hide resolved

## Step-by-steps

> [!IMPORTANT]
> This guide assumes you used the template wizard or `dotnet new unoapp` to create your solution. If not, it is recommended that you follow the [instructions](xref:Overview.Extensions) for creating an application from the template.

### 1. Preparing for custom endpoint options

* Create a new class called `CustomEndpointOptions` in the shared project. This class extends `EndpointOptions` to allow you to specify custom options for the endpoint

```csharp
public class CustomEndpointOptions : EndpointOptions
{
public string ApiKey { get; set; }
}
```

* In this example, we intend to add an access token to the request header. The `ApiKey` property will be used to store the token.

* The `EndpointOptions` class is a base class that provides a `Url` property. This property is used to specify the URL of the endpoint.

* Subclassing `EndpointOptions` will allow you to configure the `HttpClient` associated with the endpoint — all from a single configuration section.

### 2. Defining the endpoint

* Enable HTTP by calling the `UseHttp()` method to register a HTTP client with the `IHostBuilder`:

```csharp
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var appBuilder = this.CreateBuilder(args)
.Configure(hostBuilder =>
{
hostBuilder.UseHttp();
});
...
```

* The `UseHttp()` extension method accepts a callback for configuring the HTTP services as its argument. We will use this callback to register endpoints with the service collection.

```csharp
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var appBuilder = this.CreateBuilder(args)
.Configure(hostBuilder =>
{
hostBuilder.UseHttp((ctx, services) => {
// Register endpoints here
});
});
...
```

* `ctx` represents the `HostBuilderContext`. This can be used to access the configuration of the host.

* `services` is an instance of `IServiceCollection`. This is used to register services with the host.

* An extension method `AddClientWithEndpoint<TInterface, TEndpoint>()` is included which allows specifying **custom endpoint options** when adding a typed client to the service collection.

* Use this extension method to register a typed client with the service collection and specify custom endpoint options of the type `CustomEndpointOptions`.

```csharp
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var appBuilder = this.CreateBuilder(args)
.Configure(hostBuilder =>
{
hostBuilder.UseHttp((ctx, services) => {
services.AddClientWithEndpoint<HttpEndpointsOneViewModel, CustomEndpointOptions>();
});
});
...
```

* Type parameter `TInterface` is the service or view model interface that will be used to access the endpoint.

* Type parameter `TEndpoint` is the type of the custom endpoint options you define. This type must be a subclass of `EndpointOptions`.

* The extension method above allows you to pass arguments for various details such as the `HostBuilderContext`, an endpoint name (which corresponds to a configuration section), and a callback for configuring the `HttpClient` associated with this endpoint.

* Add this information to the method call as shown below:

```csharp
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var appBuilder = this.CreateBuilder(args)
.Configure(hostBuilder =>
{
hostBuilder.UseHttp((ctx, services) => {
services.AddClientWithEndpoint<HttpEndpointsOneViewModel, CustomEndpointOptions>(
ctx,
name: "HttpDummyJsonEndpoint",
configure: (builder, options) =>
{
builder.ConfigureHttpClient(client =>
{
// Configure the HttpClient here
});
}
);
});
});
...
```

* We assigned the endpoint a name of `HttpDummyJsonEndpoint`. This name corresponds to a configuration section in the `appsettings.json` file. We will add this section in the next section.

* The `configure` callback is used to configure the `HttpClient` associated with the endpoint.

> [!TIP]
> This callback is optional. If you do not need to configure the `HttpClient`, you can omit this callback.

* Notice that the callback accepts two arguments: `builder` and `options`. `options` is an instance of `CustomEndpointOptions` which we defined earlier. We will use this to access the custom options you defined in the previous section.

* Add an `ApiKey` to the request headers on the client using the `ConfigureHttpClient` method.

```csharp
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var appBuilder = this.CreateBuilder(args)
.Configure(hostBuilder =>
{
hostBuilder.UseHttp((ctx, services) => {
services.AddClientWithEndpoint<HttpEndpointsOneViewModel, CustomEndpointOptions>(
ctx,
name: "HttpDummyJsonEndpoint",
configure: (builder, options) =>
{
builder.ConfigureHttpClient(client =>
{
if (options?.ApiKey is not null)
{
client.DefaultRequestHeaders.Add("ApiKey", options.ApiKey);
}
});
}
);
});
});
...
```

* The `ApiKey` header is added to the `HttpClient` using the `DefaultRequestHeaders` property.

* The value of the header is set to the `ApiKey` property of the `CustomEndpointOptions` instance.

* We have successfully registered an endpoint with the service collection. We will now add a configuration section for this endpoint.

### 3. Adding a configuration section for the endpoint

* Open the `appsettings.json` file and add a configuration section for the endpoint:

```json
{
"HttpDummyJsonEndpoint": {
"Url": "https://DummyJson.com",
"UseNativeHandler": true,
"ApiKey": "FakeApiKey"
}
}
```

* The name of the configuration section _must_ match the name of the endpoint you specified in the previous section.

* The `Url` property is used to specify the URL of the endpoint.

* The `ApiKey` property is used to specify the API key that will be added to the request header.

* The `UseNativeHandler` property is used to explicitly specify whether to use the native HTTP handler.

### 4. Using the endpoint

* We will now use the endpoint in a view model. Create and a `HttpEndpointsOneViewModel` class with a constructor that accepts an instance of `HttpClient` like so:

```csharp
public class HttpEndpointsOneViewModel
{
private readonly HttpClient _client;

public string? Data { get; internal set;}

public HttpEndpointsOneViewModel(HttpClient client)
{
_client = client;
}

public async Task Load()
{
Data = await _client.GetStringAsync("products");
}
}
```
* The `HttpClient` instance is injected into the view model. This instance is configured with the options we specified in the previous sections.

* All the details of `IHttpClientFactory` are abstracted away from the view model. The view model can simply use this `HttpClient` instance to make requests to the endpoint. The instance can have a managed lifecycle, while a significant amount of ceremony and unintuitive workarounds are avoided.

## See also

- [How-To: Register an Endpoint for HTTP Requests](xref:Learn.Tutorials.Http.HowToHttp)
- [How-To: Consume a web API with HttpClient](xref:Uno.Development.ConsumeWebApi)
- [How-To: Create a Strongly-Typed REST Client for an API](xref:Learn.Tutorials.Http.HowToRefit)
- [Overview: HTTP](xref:Overview.Http)
- [Overview: Polly and HttpClientFactory](https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory)
- [Explore: TestHarness HTTP Endpoints](https://github.com/unoplatform/uno.extensions/tree/main/testing/TestHarness/TestHarness.Shared/Ext/Http/Endpoints)
7 changes: 5 additions & 2 deletions doc/Learn/Tutorials/Http/HowTo-Http.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,8 @@ When working with a complex application, centralized registration of your API en
## See also

- [How-To: Consume a web API with HttpClient](xref:Uno.Development.ConsumeWebApi)
- [Polly and HttpClientFactory](https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory)
- [How-To: Quickly Create a Strongly-Typed REST Client for an API](xref:Learn.Tutorials.Http.HowToRefit)
- [How-To: Create a Strongly-Typed REST Client for an API](xref:Learn.Tutorials.Http.HowToRefit)
- [How-To: Configure with Custom Endpoint Options](xref:Learn.Tutorials.Http.HowToEndpointOptions)
- [Overview: HTTP](xref:Overview.Http)
- [Overview: Polly and HttpClientFactory](https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory)
- [Explore: TestHarness HTTP Endpoints](https://github.com/unoplatform/uno.extensions/tree/main/testing/TestHarness/TestHarness.Shared/Ext/Http/Endpoints)
13 changes: 8 additions & 5 deletions doc/Learn/Tutorials/Http/HowTo-Refit.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,11 @@ When accessing resources with a [REST-style](https://www.ics.uci.edu/~fielding/p

## See also

* [Use HttpClientFactory to implement resilient HTTP requests](https://learn.microsoft.com/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#benefits-of-using-ihttpclientfactory)
* [Microsoft.Extensions.Http](https://www.nuget.org/packages/Microsoft.Extensions.Http)
* [Refit](https://github.com/reactiveui/refit)
* [Hoppscotch](https://hoppscotch.io)
* [quicktype](https://app.quicktype.io/)
- [How-To: Consume a web API with HttpClient](xref:Uno.Development.ConsumeWebApi)
- [How-To: Register an Endpoint for HTTP Requests](xref:Learn.Tutorials.Http.HowToHttp)
- [Overview: HTTP](xref:Overview.Http)
- [Overview: Use HttpClientFactory to implement resilient HTTP requests](https://learn.microsoft.com/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#benefits-of-using-ihttpclientfactory)
- [Overview: What is Refit?](https://github.com/reactiveui/refit)
- [Explore: Hoppscotch tool](https://hoppscotch.io)
- [Explore: quicktype tool](https://app.quicktype.io/)
- [Explore: TestHarness Refit Endpoints](https://github.com/unoplatform/uno.extensions/tree/main/testing/TestHarness/TestHarness.Shared/Ext/Http/Refit)
62 changes: 43 additions & 19 deletions doc/Overview/Http/HttpOverview.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,35 @@ uid: Overview.Http
---
# HTTP

Uno.Extensions.Http allows for the registration of API endpoints as multiple typed `HttpClient` instances. In this centralized location for accessing web resources, the lifecycle of the corresponding `HttpMessageHandler` objects is managed. Added clients can optionally be configured to use the platform-native handler. Additional functionality is provided to clear cookies or log diagnostic messages in responses. This library uses [Microsoft.Extensions.Http](https://www.nuget.org/packages/Microsoft.Extensions.Http) for any HTTP related work.
Uno.Extensions.Http allows for the registration of API **endpoints** as multiple typed `HttpClient` instances. In this centralized location for accessing web resources, the lifecycle of the corresponding `HttpMessageHandler` objects is managed. Added clients can optionally be configured to use the platform-native handler. Additional functionality is provided to clear cookies or log diagnostic messages in responses. This library uses [Microsoft.Extensions.Http](https://www.nuget.org/packages/Microsoft.Extensions.Http) for any HTTP related work.

For more documentation on HTTP requests, read the references listed at the bottom.
For additional documentation on HTTP requests, read the references listed at the bottom.

## Register Endpoints

Web resources exposed through an API are defined in the application as clients. These client registrations include type arguments and endpoints to be used for the client. The endpoint is defined in the `EndpointOptions` class. While it uses the platform-native HTTP handler by default, this value can be configured.

```csharp
private IHost Host { get; }
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var builder = this.CreateBuilder(args)
.Configure(host => {
host
.UseHttp((context, services) =>
{
services
.AddClient<IShowService, ShowService>(context, "configsectionname");
});
});
...
```

> [!TIP]
> If configuration sections are already used elsewhere, continuing to use that approach offers uniformity and broader accessibility of endpoint options. Consider whether this type of access is needed before using the alternate method below.
nickrandolph marked this conversation as resolved.
Show resolved Hide resolved

`EndpointOptions` can also be loaded from a specific instance.

```csharp
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var builder = this.CreateBuilder(args)
Expand All @@ -33,28 +51,28 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
...
```

`EndpointOptions` can also be loaded from a specified configuration section name. Refer to the [Configuration](xref:Overview.Configuration) documentation for more information.
### Custom Endpoint Options

```csharp
private IHost Host { get; }
`EndpointOptions` is a base class that provides a `Url` property. This property is used to specify the URL of the endpoint. Subclassing `EndpointOptions` allows for custom options beyond the `Url` such as a proxy, timeout, and adding headers. Using this method, the `HttpClient` associated with the endpoint can be configured from a single section in `appsettings.json`.

```csharp
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var builder = this.CreateBuilder(args)
.Configure(host => {
host
.UseHttp((context, services) =>
{
services
.AddClient<IShowService, ShowService>(context, "configsectionname");
var appBuilder = this.CreateBuilder(args)
.Configure(hostBuilder =>
{
hostBuilder.UseHttp((ctx, services) => {
services.AddClientWithEndpoint<IShowService, ShowService, CustomEndpointOptions>();
});
});
...
```

See the [tutorial](xref:Learn.Tutorials.Http.HowToEndpointOptions) for more information about configuring `HttpClient` with custom endpoint options.

## Refit

Refit endpoints can be configured as services in a similar way.
Similarly, **Refit endpoints** can be registered as services and configured in a similar way.

```csharp
private IHost Host { get; }
Expand All @@ -73,7 +91,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
...
```

In this case, the `EndpointOptions` will be loaded from configuration section ChuckNorrisEndpoint. The configuration section could be defined as follows:
In this case, the endpoint options will be loaded from configuration section _ChuckNorrisEndpoint_ which can be defined as the following JSON:

```json
{
Expand All @@ -87,7 +105,13 @@ In this case, the `EndpointOptions` will be loaded from configuration section Ch
See the [tutorial](xref:Learn.Tutorials.Http.HowToRefit) for more information on using Refit.

## References
- [Making HTTP requests using IHttpClientFactory](https://learn.microsoft.com/aspnet/core/fundamentals/http-requests)
- [Delegating handlers](https://learn.microsoft.com/aspnet/web-api/overview/advanced/http-message-handlers)
- [Polly and HttpClientFactory](https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory)
- [What is Refit?](https://github.com/reactiveui/refit)

- [How-To: Consume a web API with HttpClient](xref:Uno.Development.ConsumeWebApi)
- [How-To: Register an Endpoint for HTTP Requests](xref:Learn.Tutorials.Http.HowToHttp)
- [How-To: Configure with Custom Endpoint Options](xref:Learn.Tutorials.Http.HowToEndpointOptions)
- [How-To: Create a Strongly-Typed REST Client for an API](xref:Learn.Tutorials.Http.HowToRefit)
- [Overview: Use HttpClientFactory to implement resilient HTTP requests](https://learn.microsoft.com/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#benefits-of-using-ihttpclientfactory)
- [Overview: Delegating handlers](https://learn.microsoft.com/aspnet/web-api/overview/advanced/http-message-handlers)
- [Overview: Polly and HttpClientFactory](https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory)
- [Overview: What is Refit?](https://github.com/reactiveui/refit)
- [Explore: TestHarness HTTP](https://github.com/unoplatform/uno.extensions/tree/main/testing/TestHarness/TestHarness.Shared/Ext/Http/)
2 changes: 2 additions & 0 deletions doc/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
href: Learn/Tutorials/Http/HowTo-Http.md
- name: Use Refit to call a REST API
href: Learn/Tutorials/Http/HowTo-Refit.md
- name: Configure with Custom Endpoint Options
href: Learn/Tutorials/Http/HowTo-EndpointOptions.md
- name: Localization
items:
- name: Overview
Expand Down
Loading