Windows make http request

Содержание
  1. WinHttpOpenRequest function (winhttp.h)
  2. Syntax
  3. Parameters
  4. Return value
  5. Remarks
  6. Examples
  7. How to receive http-requests with Windows Service
  8. 1 Answer 1
  9. Make HTTP requests using IHttpClientFactory in ASP.NET Core
  10. Consumption patterns
  11. Basic usage
  12. Named clients
  13. CreateClient
  14. Typed clients
  15. Generated clients
  16. Make POST, PUT, and DELETE requests
  17. Outgoing request middleware
  18. Use DI in outgoing request middleware
  19. Use Polly-based handlers
  20. Handle transient faults
  21. Dynamically select policies
  22. Add multiple Polly handlers
  23. Add policies from the Polly registry
  24. HttpClient and lifetime management
  25. Alternatives to IHttpClientFactory
  26. Cookies
  27. Logging
  28. Configure the HttpMessageHandler
  29. Use IHttpClientFactory in a console app
  30. Header propagation middleware
  31. Additional resources
  32. Consumption patterns
  33. Basic usage
  34. Named clients
  35. Typed clients
  36. Generated clients
  37. Outgoing request middleware
  38. Use Polly-based handlers
  39. Handle transient faults
  40. Dynamically select policies
  41. Add multiple Polly handlers
  42. Add policies from the Polly registry
  43. HttpClient and lifetime management
  44. Alternatives to IHttpClientFactory
  45. Cookies
  46. Logging
  47. Configure the HttpMessageHandler
  48. Use IHttpClientFactory in a console app
  49. Additional resources
  50. Prerequisites
  51. Consumption patterns
  52. Basic usage
  53. Named clients
  54. Typed clients
  55. Generated clients
  56. Outgoing request middleware
  57. Use Polly-based handlers
  58. Handle transient faults
  59. Dynamically select policies
  60. Add multiple Polly handlers
  61. Add policies from the Polly registry
  62. HttpClient and lifetime management
  63. Alternatives to IHttpClientFactory
  64. Cookies
  65. Logging
  66. Configure the HttpMessageHandler
  67. Use IHttpClientFactory in a console app
  68. Header propagation middleware

WinHttpOpenRequest function (winhttp.h)

The WinHttpOpenRequest function creates an HTTP request handle.

Syntax

Parameters

HINTERNET connection handle to an HTTP session returned by WinHttpConnect.

Pointer to a string that contains the HTTP verb to use in the request. If this parameter is NULL, the function uses GET as the HTTP verb. NoteВ В This string should be all uppercase. Many servers treat HTTP verbs as case-sensitive, and the Internet Engineering Task Force (IETF) Requests for Comments (RFCs) spell these verbs using uppercase characters only.

Pointer to a string that contains the name of the target resource of the specified HTTP verb. This is generally a file name, an executable module, or a search specifier.

Pointer to a string that contains the HTTP version. If this parameter is NULL, the function uses HTTP/1.1.

Pointer to a string that specifies the URL of the document from which the URL in the request pwszObjectName was obtained. If this parameter is set to WINHTTP_NO_REFERER, no referring document is specified.

Pointer to a null-terminated array of string pointers that specifies media types accepted by the client. If this parameter is set to WINHTTP_DEFAULT_ACCEPT_TYPES, no types are accepted by the client. Typically, servers handle a lack of accepted types as indication that the client accepts only documents of type «text/*»; that is, only text documents—no pictures or other binary files. For a list of valid media types, see Media Types defined by IANA at http://www.iana.org/assignments/media-types/.

Unsigned long integer value that contains the Internet flag values. This can be one or more of the following values:

Value Meaning
WINHTTP_FLAG_BYPASS_PROXY_CACHE This flag provides the same behavior as WINHTTP_FLAG_REFRESH.
WINHTTP_FLAG_ESCAPE_DISABLE Unsafe characters in the URL passed in for pwszObjectName are not converted to escape sequences.
WINHTTP_FLAG_ESCAPE_DISABLE_QUERY Unsafe characters in the query component of the URL passed in for pwszObjectName are not converted to escape sequences.
WINHTTP_FLAG_ESCAPE_PERCENT The string passed in for pwszObjectName is converted from an LPCWSTR to an LPSTR. All unsafe characters are converted to an escape sequence including the percent symbol. By default, all unsafe characters except the percent symbol are converted to an escape sequence.
WINHTTP_FLAG_NULL_CODEPAGE The string passed in for pwszObjectName is assumed to consist of valid ANSI characters represented by WCHAR. No check are done for unsafe characters.

WindowsВ 7:В В This option is obsolete.

WINHTTP_FLAG_REFRESH Indicates that the request should be forwarded to the originating server rather than sending a cached version of a resource from a proxy server. When this flag is used, a «Pragma: no-cache» header is added to the request handle. When creating an HTTP/1.1 request header, a «Cache-Control: no-cache» is also added.
WINHTTP_FLAG_SECURE Uses secure transaction semantics. This translates to using Secure Sockets Layer (SSL)/Transport Layer Security (TLS).

Return value

Returns a valid HTTP request handle if successful, or NULL if not. For extended error information, call GetLastError. Among the error codes returned are the following.

Error Code Description
ERROR_WINHTTP_INCORRECT_HANDLE_TYPE The type of handle supplied is incorrect for this operation.
ERROR_WINHTTP_INTERNAL_ERROR An internal error has occurred.
ERROR_WINHTTP_INVALID_URL The URL is invalid.
ERROR_WINHTTP_OPERATION_CANCELLED The operation was canceled, usually because the handle on which the request was operating was closed before the operation completed.
ERROR_WINHTTP_UNRECOGNIZED_SCHEME The URL specified a scheme other than «http:» or «https:».
ERROR_NOT_ENOUGH_MEMORY Not enough memory was available to complete the requested operation. (Windows error code)

Remarks

The return value indicates success or failure. To get extended error information, call GetLastError.

The WinHttpOpenRequest function creates a new HTTP request handle and stores the specified parameters in that handle. An HTTP request handle holds a request to send to an HTTP server and contains all RFC822/MIME/HTTP headers to be sent as part of the request.

If pwszVerb is set to «HEAD», the Content-Length header is ignored.

If a status callback function has been installed with WinHttpSetStatusCallback, then a WINHTTP_CALLBACK_STATUS_HANDLE_CREATED notification indicates that WinHttpOpenRequest has created a request handle.

After the calling application finishes using the HINTERNET handle returned by WinHttpOpenRequest, it must be closed using the WinHttpCloseHandle function.

Examples

This example shows how to obtain an HINTERNET handle, open an HTTP session, create a request header, and send that header to the server.

How to receive http-requests with Windows Service

I’m new at winservice developing. I need to create service, that will contain some functions and api: it should do something when some requests are coming.

As I understand, I need to use HttpListener class.

Base of this problem is described here: How to create a HTTP request listener Windows Service in .NET But it isn’t finished code and I have more questions.

So, I added some log-writtings to understand what is happening:

When I reinstall and start service I open http://localhost:1111/ and -sometimes everything is OK, I see HelloWorld in my browser and I see 1234 in MyLog -sometimes it stucks and I wait and wait and wait. In that time I can see «1» in log, but not «2». Nothing happens, if I just wait. But if I load localhost again(or press f5), I can see Hello World. And log just adds 234 and it is 1234 and not 11234 summary..

After that, it just stucks and nothing changes in log till I restart service.

So, I understood, that problem may be with listener.EndGetContext , that is not used yet.

I tested this changing in code(last two lines):

This bad code works: when HelloWorld is shown, I can reload page and see it again(and log shows 1234 many time). But I still have problem with 1. 234(for some loadings of HelloWorld it appears, for some doesn’t)

Of course, using BeginGetContext in the end of callback is very bad, but it is just my test.

Also, I need to work with parallel requests

So, how to do it in right way? I can’t find anything helpfull about winservices requests receiving in google.. Just that question about HttpListener..

I tried new way:

It is allmost ok, it waits when context appeas, works with it and waits again. But this way creates a queue of requests. If 1000 requests come in one time, the last will be processed after 999. It is better than nothing, but I want to process requests parallel. Also, very strange things happen: I’ve created simple web-server with some routes, but after some time of using, it hangs. Nothing changes till I reinstall(not restart!) service..

So, I’m still waiting for any really good realization, that will contain

  • Parallel processing incoming requests
  • Clear and simple code for understanding callbacks and so on
  • Stopping service shouldn’t make any problems with while loop

1 Answer 1

You really want to use the asynchronous API for this — in fact, this is almost exactly what it has been designed for.

The basic code is relatively simple:

If you have no experience with the await pattern, you might want to read up a bit on the particulars. And of course, this is definitely not production code, just a sample. You need to add a lot more error handling for starters.

Also of note is that because HandleRequest is not awaited, you can’t handle its exceptions in the StartListener method — you’ll either have to add a continuation for handling those, or you should just catch the exceptions in HandleRequest directly.

The basic idea is simple — by not awaiting the HandleRequest method, we’re immediately going back to waiting for a new request (by asynchronous I/O, i.e. thread-less). If HttpListener is really meant to be used this way (and I believe it is, although I haven’t tested it), it will allow you to process many requests in parallel, both with asynchronous I/O and CPU work.

The ConfigureAwait makes sure you’re not going to get synchronized to a SynchronizationContext . This isn’t strictly necessary in a service / console application, because those often don’t have any synchronization contexts, but I tend to write it out explicitly anyway. The continuation of the GetContextAsync method is thus posted to a different task scheduler — by default, a thread pool task scheduler.

Make HTTP requests using IHttpClientFactory in ASP.NET Core

An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. IHttpClientFactory offers the following benefits:

  • Provides a central location for naming and configuring logical HttpClient instances. For example, a client named github could be registered and configured to access GitHub. A default client can be registered for general access.
  • Codifies the concept of outgoing middleware via delegating handlers in HttpClient . Provides extensions for Polly-based middleware to take advantage of delegating handlers in HttpClient .
  • Manages the pooling and lifetime of underlying HttpClientMessageHandler instances. Automatic management avoids common DNS (Domain Name System) problems that occur when manually managing HttpClient lifetimes.
  • Adds a configurable logging experience (via ILogger ) for all requests sent through clients created by the factory.

The sample code in this topic version uses System.Text.Json to deserialize JSON content returned in HTTP responses. For samples that use Json.NET and ReadAsAsync , use the version selector to select a 2.x version of this topic.

Consumption patterns

There are several ways IHttpClientFactory can be used in an app:

The best approach depends upon the app’s requirements.

Basic usage

IHttpClientFactory can be registered by calling AddHttpClient :

An IHttpClientFactory can be requested using dependency injection (DI). The following code uses IHttpClientFactory to create an HttpClient instance:

Using IHttpClientFactory like in the preceding example is a good way to refactor an existing app. It has no impact on how HttpClient is used. In places where HttpClient instances are created in an existing app, replace those occurrences with calls to CreateClient.

Named clients

Named clients are a good choice when:

  • The app requires many distinct uses of HttpClient .
  • Many HttpClient s have different configuration.

Configuration for a named HttpClient can be specified during registration in Startup.ConfigureServices :

In the preceding code the client is configured with:

  • The base address https://api.github.com/ .
  • Two headers required to work with the GitHub API.

CreateClient

Each time CreateClient is called:

  • A new instance of HttpClient is created.
  • The configuration action is called.

To create a named client, pass its name into CreateClient :

In the preceding code, the request doesn’t need to specify a hostname. The code can pass just the path, since the base address configured for the client is used.

Typed clients

  • Provide the same capabilities as named clients without the need to use strings as keys.
  • Provides IntelliSense and compiler help when consuming clients.
  • Provide a single location to configure and interact with a particular HttpClient . For example, a single typed client might be used:
    • For a single backend endpoint.
    • To encapsulate all logic dealing with the endpoint.
  • Work with DI and can be injected where required in the app.

A typed client accepts an HttpClient parameter in its constructor:

If you would like to see code comments translated to languages other than English, let us know in this GitHub discussion issue.

In the preceding code:

  • The configuration is moved into the typed client.
  • The HttpClient object is exposed as a public property.

API-specific methods can be created that expose HttpClient functionality. For example, the GetAspNetDocsIssues method encapsulates code to retrieve open issues.

The following code calls AddHttpClient in Startup.ConfigureServices to register a typed client class:

The typed client is registered as transient with DI. In the preceding code, AddHttpClient registers GitHubService as a transient service. This registration uses a factory method to:

  1. Create an instance of HttpClient .
  2. Create an instance of GitHubService , passing in the instance of HttpClient to its constructor.

The typed client can be injected and consumed directly:

The configuration for a typed client can be specified during registration in Startup.ConfigureServices , rather than in the typed client’s constructor:

The HttpClient can be encapsulated within a typed client. Rather than exposing it as a property, define a method which calls the HttpClient instance internally:

In the preceding code, the HttpClient is stored in a private field. Access to the HttpClient is by the public GetRepos method.

Generated clients

IHttpClientFactory can be used in combination with third-party libraries such as Refit. Refit is a REST library for .NET. It converts REST APIs into live interfaces. An implementation of the interface is generated dynamically by the RestService , using HttpClient to make the external HTTP calls.

An interface and a reply are defined to represent the external API and its response:

A typed client can be added, using Refit to generate the implementation:

The defined interface can be consumed where necessary, with the implementation provided by DI and Refit:

Make POST, PUT, and DELETE requests

In the preceding examples, all HTTP requests use the GET HTTP verb. HttpClient also supports other HTTP verbs, including:

For a complete list of supported HTTP verbs, see HttpMethod.

The following example shows how to make an HTTP POST request:

In the preceding code, the CreateItemAsync method:

  • Serializes the TodoItem parameter to JSON using System.Text.Json . This uses an instance of JsonSerializerOptions to configure the serialization process.
  • Creates an instance of StringContent to package the serialized JSON for sending in the HTTP request’s body.
  • Calls PostAsync to send the JSON content to the specified URL. This is a relative URL that gets added to the HttpClient.BaseAddress.
  • Calls EnsureSuccessStatusCode to throw an exception if the response status code does not indicate success.

HttpClient also supports other types of content. For example, MultipartContent and StreamContent. For a complete list of supported content, see HttpContent.

The following example shows an HTTP PUT request:

The preceding code is very similar to the POST example. The SaveItemAsync method calls PutAsync instead of PostAsync .

The following example shows an HTTP DELETE request:

In the preceding code, the DeleteItemAsync method calls DeleteAsync. Because HTTP DELETE requests typically contain no body, the DeleteAsync method doesn’t provide an overload that accepts an instance of HttpContent .

To learn more about using different HTTP verbs with HttpClient , see HttpClient.

Outgoing request middleware

HttpClient has the concept of delegating handlers that can be linked together for outgoing HTTP requests. IHttpClientFactory :

Simplifies defining the handlers to apply for each named client.

Supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. Each of these handlers is able to perform work before and after the outgoing request. This pattern:

  • Is similar to the inbound middleware pipeline in ASP.NET Core.
  • Provides a mechanism to manage cross-cutting concerns around HTTP requests, such as:
    • caching
    • error handling
    • serialization
    • logging

To create a delegating handler:

  • Derive from DelegatingHandler.
  • Override SendAsync. Execute code before passing the request to the next handler in the pipeline:

The preceding code checks if the X-API-KEY header is in the request. If X-API-KEY is missing, BadRequest is returned.

In the preceding code, the ValidateHeaderHandler is registered with DI. Once registered, AddHttpMessageHandler can be called, passing in the type for the handler.

Multiple handlers can be registered in the order that they should execute. Each handler wraps the next handler until the final HttpClientHandler executes the request:

Use DI in outgoing request middleware

When IHttpClientFactory creates a new delegating handler, it uses DI to fulfill the handler’s constructor parameters. IHttpClientFactory creates a separate DI scope for each handler, which can lead to surprising behavior when a handler consumes a scoped service.

For example, consider the following interface and its implementation, which represents a task as an operation with an identifier, OperationId :

As its name suggests, IOperationScoped is registered with DI using a scoped lifetime:

The following delegating handler consumes and uses IOperationScoped to set the X-OPERATION-ID header for the outgoing request:

In the HttpRequestsSample download], navigate to /Operation and refresh the page. The request scope value changes for each request, but the handler scope value only changes every 5 seconds.

Handlers can depend upon services of any scope. Services that handlers depend upon are disposed when the handler is disposed.

Use one of the following approaches to share per-request state with message handlers:

  • Pass data into the handler using HttpRequestMessage.Properties.
  • Use IHttpContextAccessor to access the current request.
  • Create a custom AsyncLocal storage object to pass the data.

Use Polly-based handlers

IHttpClientFactory integrates with the third-party library Polly. Polly is a comprehensive resilience and transient fault-handling library for .NET. It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. The Polly extensions support adding Polly-based handlers to clients. Polly requires the Microsoft.Extensions.Http.Polly NuGet package.

Handle transient faults

Faults typically occur when external HTTP calls are transient. AddTransientHttpErrorPolicy allows a policy to be defined to handle transient errors. Policies configured with AddTransientHttpErrorPolicy handle the following responses:

AddTransientHttpErrorPolicy provides access to a PolicyBuilder object configured to handle errors representing a possible transient fault:

In the preceding code, a WaitAndRetryAsync policy is defined. Failed requests are retried up to three times with a delay of 600 ms between attempts.

Dynamically select policies

Extension methods are provided to add Polly-based handlers, for example, AddPolicyHandler. The following AddPolicyHandler overload inspects the request to decide which policy to apply:

In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. For any other HTTP method, a 30-second timeout is used.

Add multiple Polly handlers

It’s common to nest Polly policies:

In the preceding example:

  • Two handlers are added.
  • The first handler uses AddTransientHttpErrorPolicy to add a retry policy. Failed requests are retried up to three times.
  • The second AddTransientHttpErrorPolicy call adds a circuit breaker policy. Further external requests are blocked for 30 seconds if 5 failed attempts occur sequentially. Circuit breaker policies are stateful. All calls through this client share the same circuit state.

Add policies from the Polly registry

An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry .

In the following code:

  • The «regular» and «long» policies are added.
  • AddPolicyHandlerFromRegistry adds the «regular» and «long» policies from the registry.

For more information on IHttpClientFactory and Polly integrations, see the Polly wiki.

HttpClient and lifetime management

A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory . An HttpMessageHandler is created per named client. The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn’t expired.

Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. Creating more handlers than necessary can result in connection delays. Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS (Domain Name System) changes.

The default handler lifetime is two minutes. The default value can be overridden on a per named client basis:

HttpClient instances can generally be treated as .NET objects not requiring disposal. Disposal cancels outgoing requests and guarantees the given HttpClient instance can’t be used after calling Dispose. IHttpClientFactory tracks and disposes resources used by HttpClient instances.

Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory . This pattern becomes unnecessary after migrating to IHttpClientFactory .

Alternatives to IHttpClientFactory

Using IHttpClientFactory in a DI-enabled app avoids:

  • Resource exhaustion problems by pooling HttpMessageHandler instances.
  • Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • The SocketsHttpHandler shares connections across HttpClient instances. This sharing prevents socket exhaustion.
  • The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

Cookies

The pooled HttpMessageHandler instances results in CookieContainer objects being shared. Unanticipated CookieContainer object sharing often results in incorrect code. For apps that require cookies, consider either:

  • Disabling automatic cookie handling
  • Avoiding IHttpClientFactory

Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

Logging

Clients created via IHttpClientFactory record log messages for all requests. Enable the appropriate information level in the logging configuration to see the default log messages. Additional logging, such as the logging of request headers, is only included at trace level.

The log category used for each client includes the name of the client. A client named MyNamedClient, for example, logs messages with a category of «System.Net.Http.HttpClient.MyNamedClient.LogicalHandler». Messages suffixed with LogicalHandler occur outside the request handler pipeline. On the request, messages are logged before any other handlers in the pipeline have processed it. On the response, messages are logged after any other pipeline handlers have received the response.

Logging also occurs inside the request handler pipeline. In the MyNamedClient example, those messages are logged with the log category «System.Net.Http.HttpClient.MyNamedClient.ClientHandler». For the request, this occurs after all other handlers have run and immediately before the request is sent. On the response, this logging includes the state of the response before it passes back through the handler pipeline.

Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. This may include changes to request headers or to the response status code.

Including the name of the client in the log category enables log filtering for specific named clients.

Configure the HttpMessageHandler

It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

An IHttpClientBuilder is returned when adding named or typed clients. The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. The delegate is used to create and configure the primary HttpMessageHandler used by that client:

Use IHttpClientFactory in a console app

In a console app, add the following package references to the project:

In the following example:

  • IHttpClientFactory is registered in the Generic Host’s service container.
  • MyService creates a client factory instance from the service, which is used to create an HttpClient . HttpClient is used to retrieve a webpage.
  • Main creates a scope to execute the service’s GetPage method and write the first 500 characters of the webpage content to the console.

Header propagation middleware

Header propagation is an ASP.NET Core middleware to propagate HTTP headers from the incoming request to the outgoing HTTP Client requests. To use header propagation:

Configure the middleware and HttpClient in Startup :

The client includes the configured headers on outbound requests:

Additional resources

An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. It offers the following benefits:

  • Provides a central location for naming and configuring logical HttpClient instances. For example, a github client can be registered and configured to access GitHub. A default client can be registered for other purposes.
  • Codifies the concept of outgoing middleware via delegating handlers in HttpClient and provides extensions for Polly-based middleware to take advantage of that.
  • Manages the pooling and lifetime of underlying HttpClientMessageHandler instances to avoid common DNS problems that occur when manually managing HttpClient lifetimes.
  • Adds a configurable logging experience (via ILogger ) for all requests sent through clients created by the factory.

Consumption patterns

There are several ways IHttpClientFactory can be used in an app:

None of them are strictly superior to another. The best approach depends upon the app’s constraints.

Basic usage

The IHttpClientFactory can be registered by calling the AddHttpClient extension method on the IServiceCollection , inside the Startup.ConfigureServices method.

Once registered, code can accept an IHttpClientFactory anywhere services can be injected with dependency injection (DI). The IHttpClientFactory can be used to create an HttpClient instance:

Using IHttpClientFactory in this fashion is a good way to refactor an existing app. It has no impact on the way HttpClient is used. In places where HttpClient instances are currently created, replace those occurrences with a call to CreateClient.

Named clients

If an app requires many distinct uses of HttpClient , each with a different configuration, an option is to use named clients. Configuration for a named HttpClient can be specified during registration in Startup.ConfigureServices .

In the preceding code, AddHttpClient is called, providing the name github. This client has some default configuration applied—namely the base address and two headers required to work with the GitHub API.

Each time CreateClient is called, a new instance of HttpClient is created and the configuration action is called.

To consume a named client, a string parameter can be passed to CreateClient . Specify the name of the client to be created:

In the preceding code, the request doesn’t need to specify a hostname. It can pass just the path, since the base address configured for the client is used.

Typed clients

  • Provide the same capabilities as named clients without the need to use strings as keys.
  • Provides IntelliSense and compiler help when consuming clients.
  • Provide a single location to configure and interact with a particular HttpClient . For example, a single typed client might be used for a single backend endpoint and encapsulate all logic dealing with that endpoint.
  • Work with DI and can be injected where required in your app.

A typed client accepts an HttpClient parameter in its constructor:

In the preceding code, the configuration is moved into the typed client. The HttpClient object is exposed as a public property. It’s possible to define API-specific methods that expose HttpClient functionality. The GetAspNetDocsIssues method encapsulates the code needed to query for and parse out the latest open issues from a GitHub repository.

To register a typed client, the generic AddHttpClient extension method can be used within Startup.ConfigureServices , specifying the typed client class:

The typed client is registered as transient with DI. The typed client can be injected and consumed directly:

If preferred, the configuration for a typed client can be specified during registration in Startup.ConfigureServices , rather than in the typed client’s constructor:

It’s possible to entirely encapsulate the HttpClient within a typed client. Rather than exposing it as a property, public methods can be provided which call the HttpClient instance internally.

In the preceding code, the HttpClient is stored as a private field. All access to make external calls goes through the GetRepos method.

Generated clients

IHttpClientFactory can be used in combination with other third-party libraries such as Refit. Refit is a REST library for .NET. It converts REST APIs into live interfaces. An implementation of the interface is generated dynamically by the RestService , using HttpClient to make the external HTTP calls.

An interface and a reply are defined to represent the external API and its response:

A typed client can be added, using Refit to generate the implementation:

The defined interface can be consumed where necessary, with the implementation provided by DI and Refit:

Outgoing request middleware

HttpClient already has the concept of delegating handlers that can be linked together for outgoing HTTP requests. The IHttpClientFactory makes it easy to define the handlers to apply for each named client. It supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. Each of these handlers is able to perform work before and after the outgoing request. This pattern is similar to the inbound middleware pipeline in ASP.NET Core. The pattern provides a mechanism to manage cross-cutting concerns around HTTP requests, including caching, error handling, serialization, and logging.

To create a handler, define a class deriving from DelegatingHandler. Override the SendAsync method to execute code before passing the request to the next handler in the pipeline:

The preceding code defines a basic handler. It checks to see if an X-API-KEY header has been included on the request. If the header is missing, it can avoid the HTTP call and return a suitable response.

During registration, one or more handlers can be added to the configuration for an HttpClient . This task is accomplished via extension methods on the IHttpClientBuilder.

In the preceding code, the ValidateHeaderHandler is registered with DI. The IHttpClientFactory creates a separate DI scope for each handler. Handlers are free to depend upon services of any scope. Services that handlers depend upon are disposed when the handler is disposed.

Once registered, AddHttpMessageHandler can be called, passing in the type for the handler.

Multiple handlers can be registered in the order that they should execute. Each handler wraps the next handler until the final HttpClientHandler executes the request:

Use one of the following approaches to share per-request state with message handlers:

  • Pass data into the handler using HttpRequestMessage.Properties .
  • Use IHttpContextAccessor to access the current request.
  • Create a custom AsyncLocal storage object to pass the data.

Use Polly-based handlers

IHttpClientFactory integrates with a popular third-party library called Polly. Polly is a comprehensive resilience and transient fault-handling library for .NET. It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. The Polly extensions:

  • Support adding Polly-based handlers to clients.
  • Can be used after installing the Microsoft.Extensions.Http.Polly NuGet package. The package isn’t included in the ASP.NET Core shared framework.

Handle transient faults

Most common faults occur when external HTTP calls are transient. A convenient extension method called AddTransientHttpErrorPolicy is included which allows a policy to be defined to handle transient errors. Policies configured with this extension method handle HttpRequestException , HTTP 5xx responses, and HTTP 408 responses.

The AddTransientHttpErrorPolicy extension can be used within Startup.ConfigureServices . The extension provides access to a PolicyBuilder object configured to handle errors representing a possible transient fault:

In the preceding code, a WaitAndRetryAsync policy is defined. Failed requests are retried up to three times with a delay of 600 ms between attempts.

Dynamically select policies

Additional extension methods exist which can be used to add Polly-based handlers. One such extension is AddPolicyHandler , which has multiple overloads. One overload allows the request to be inspected when defining which policy to apply:

In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. For any other HTTP method, a 30-second timeout is used.

Add multiple Polly handlers

It’s common to nest Polly policies to provide enhanced functionality:

In the preceding example, two handlers are added. The first uses the AddTransientHttpErrorPolicy extension to add a retry policy. Failed requests are retried up to three times. The second call to AddTransientHttpErrorPolicy adds a circuit breaker policy. Further external requests are blocked for 30 seconds if five failed attempts occur sequentially. Circuit breaker policies are stateful. All calls through this client share the same circuit state.

Add policies from the Polly registry

An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry . An extension method is provided which allows a handler to be added using a policy from the registry:

In the preceding code, two policies are registered when the PolicyRegistry is added to the ServiceCollection . To use a policy from the registry, the AddPolicyHandlerFromRegistry method is used, passing the name of the policy to apply.

Further information about IHttpClientFactory and Polly integrations can be found on the Polly wiki.

HttpClient and lifetime management

A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory . There’s an HttpMessageHandler per named client. The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn’t expired.

Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. Creating more handlers than necessary can result in connection delays. Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

The default handler lifetime is two minutes. The default value can be overridden on a per named client basis. To override it, call SetHandlerLifetime on the IHttpClientBuilder that is returned when creating the client:

Disposal of the client isn’t required. Disposal cancels outgoing requests and guarantees the given HttpClient instance can’t be used after calling Dispose. IHttpClientFactory tracks and disposes resources used by HttpClient instances. The HttpClient instances can generally be treated as .NET objects not requiring disposal.

Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory . This pattern becomes unnecessary after migrating to IHttpClientFactory .

Alternatives to IHttpClientFactory

Using IHttpClientFactory in a DI-enabled app avoids:

  • Resource exhaustion problems by pooling HttpMessageHandler instances.
  • Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • The SocketsHttpHandler shares connections across HttpClient instances. This sharing prevents socket exhaustion.
  • The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

Cookies

The pooled HttpMessageHandler instances results in CookieContainer objects being shared. Unanticipated CookieContainer object sharing often results in incorrect code. For apps that require cookies, consider either:

  • Disabling automatic cookie handling
  • Avoiding IHttpClientFactory

Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

Logging

Clients created via IHttpClientFactory record log messages for all requests. Enable the appropriate information level in your logging configuration to see the default log messages. Additional logging, such as the logging of request headers, is only included at trace level.

The log category used for each client includes the name of the client. A client named MyNamedClient, for example, logs messages with a category of System.Net.Http.HttpClient.MyNamedClient.LogicalHandler . Messages suffixed with LogicalHandler occur outside the request handler pipeline. On the request, messages are logged before any other handlers in the pipeline have processed it. On the response, messages are logged after any other pipeline handlers have received the response.

Logging also occurs inside the request handler pipeline. In the MyNamedClient example, those messages are logged against the log category System.Net.Http.HttpClient.MyNamedClient.ClientHandler . For the request, this occurs after all other handlers have run and immediately before the request is sent out on the network. On the response, this logging includes the state of the response before it passes back through the handler pipeline.

Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. This may include changes to request headers, for example, or to the response status code.

Including the name of the client in the log category enables log filtering for specific named clients where necessary.

Configure the HttpMessageHandler

It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

An IHttpClientBuilder is returned when adding named or typed clients. The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. The delegate is used to create and configure the primary HttpMessageHandler used by that client:

Use IHttpClientFactory in a console app

In a console app, add the following package references to the project:

In the following example:

  • IHttpClientFactory is registered in the Generic Host’s service container.
  • MyService creates a client factory instance from the service, which is used to create an HttpClient . HttpClient is used to retrieve a webpage.
  • Main creates a scope to execute the service’s GetPage method and write the first 500 characters of the webpage content to the console.

Additional resources

An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. It offers the following benefits:

  • Provides a central location for naming and configuring logical HttpClient instances. For example, a github client can be registered and configured to access GitHub. A default client can be registered for other purposes.
  • Codifies the concept of outgoing middleware via delegating handlers in HttpClient and provides extensions for Polly-based middleware to take advantage of that.
  • Manages the pooling and lifetime of underlying HttpClientMessageHandler instances to avoid common DNS problems that occur when manually managing HttpClient lifetimes.
  • Adds a configurable logging experience (via ILogger ) for all requests sent through clients created by the factory.

Prerequisites

Projects targeting .NET Framework require installation of the Microsoft.Extensions.Http NuGet package. Projects that target .NET Core and reference the Microsoft.AspNetCore.App metapackage already include the Microsoft.Extensions.Http package.

Consumption patterns

There are several ways IHttpClientFactory can be used in an app:

None of them are strictly superior to another. The best approach depends upon the app’s constraints.

Basic usage

The IHttpClientFactory can be registered by calling the AddHttpClient extension method on the IServiceCollection , inside the Startup.ConfigureServices method.

Once registered, code can accept an IHttpClientFactory anywhere services can be injected with dependency injection (DI). The IHttpClientFactory can be used to create an HttpClient instance:

Using IHttpClientFactory in this fashion is a good way to refactor an existing app. It has no impact on the way HttpClient is used. In places where HttpClient instances are currently created, replace those occurrences with a call to CreateClient.

Named clients

If an app requires many distinct uses of HttpClient , each with a different configuration, an option is to use named clients. Configuration for a named HttpClient can be specified during registration in Startup.ConfigureServices .

In the preceding code, AddHttpClient is called, providing the name github. This client has some default configuration applied—namely the base address and two headers required to work with the GitHub API.

Each time CreateClient is called, a new instance of HttpClient is created and the configuration action is called.

To consume a named client, a string parameter can be passed to CreateClient . Specify the name of the client to be created:

In the preceding code, the request doesn’t need to specify a hostname. It can pass just the path, since the base address configured for the client is used.

Typed clients

  • Provide the same capabilities as named clients without the need to use strings as keys.
  • Provides IntelliSense and compiler help when consuming clients.
  • Provide a single location to configure and interact with a particular HttpClient . For example, a single typed client might be used for a single backend endpoint and encapsulate all logic dealing with that endpoint.
  • Work with DI and can be injected where required in your app.

A typed client accepts an HttpClient parameter in its constructor:

In the preceding code, the configuration is moved into the typed client. The HttpClient object is exposed as a public property. It’s possible to define API-specific methods that expose HttpClient functionality. The GetAspNetDocsIssues method encapsulates the code needed to query for and parse out the latest open issues from a GitHub repository.

To register a typed client, the generic AddHttpClient extension method can be used within Startup.ConfigureServices , specifying the typed client class:

The typed client is registered as transient with DI. The typed client can be injected and consumed directly:

If preferred, the configuration for a typed client can be specified during registration in Startup.ConfigureServices , rather than in the typed client’s constructor:

It’s possible to entirely encapsulate the HttpClient within a typed client. Rather than exposing it as a property, public methods can be provided which call the HttpClient instance internally.

In the preceding code, the HttpClient is stored as a private field. All access to make external calls goes through the GetRepos method.

Generated clients

IHttpClientFactory can be used in combination with other third-party libraries such as Refit. Refit is a REST library for .NET. It converts REST APIs into live interfaces. An implementation of the interface is generated dynamically by the RestService , using HttpClient to make the external HTTP calls.

An interface and a reply are defined to represent the external API and its response:

A typed client can be added, using Refit to generate the implementation:

The defined interface can be consumed where necessary, with the implementation provided by DI and Refit:

Outgoing request middleware

HttpClient already has the concept of delegating handlers that can be linked together for outgoing HTTP requests. The IHttpClientFactory makes it easy to define the handlers to apply for each named client. It supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. Each of these handlers is able to perform work before and after the outgoing request. This pattern is similar to the inbound middleware pipeline in ASP.NET Core. The pattern provides a mechanism to manage cross-cutting concerns around HTTP requests, including caching, error handling, serialization, and logging.

To create a handler, define a class deriving from DelegatingHandler. Override the SendAsync method to execute code before passing the request to the next handler in the pipeline:

The preceding code defines a basic handler. It checks to see if an X-API-KEY header has been included on the request. If the header is missing, it can avoid the HTTP call and return a suitable response.

During registration, one or more handlers can be added to the configuration for an HttpClient . This task is accomplished via extension methods on the IHttpClientBuilder.

In the preceding code, the ValidateHeaderHandler is registered with DI. The handler must be registered in DI as a transient service, never scoped. If the handler is registered as a scoped service and any services that the handler depends upon are disposable:

  • The handler’s services could be disposed before the handler goes out of scope.
  • The disposed handler services causes the handler to fail.

Once registered, AddHttpMessageHandler can be called, passing in the handler type.

Multiple handlers can be registered in the order that they should execute. Each handler wraps the next handler until the final HttpClientHandler executes the request:

Use one of the following approaches to share per-request state with message handlers:

  • Pass data into the handler using HttpRequestMessage.Properties .
  • Use IHttpContextAccessor to access the current request.
  • Create a custom AsyncLocal storage object to pass the data.

Use Polly-based handlers

IHttpClientFactory integrates with a popular third-party library called Polly. Polly is a comprehensive resilience and transient fault-handling library for .NET. It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. The Polly extensions:

  • Support adding Polly-based handlers to clients.
  • Can be used after installing the Microsoft.Extensions.Http.Polly NuGet package. The package isn’t included in the ASP.NET Core shared framework.

Handle transient faults

Most common faults occur when external HTTP calls are transient. A convenient extension method called AddTransientHttpErrorPolicy is included which allows a policy to be defined to handle transient errors. Policies configured with this extension method handle HttpRequestException , HTTP 5xx responses, and HTTP 408 responses.

The AddTransientHttpErrorPolicy extension can be used within Startup.ConfigureServices . The extension provides access to a PolicyBuilder object configured to handle errors representing a possible transient fault:

In the preceding code, a WaitAndRetryAsync policy is defined. Failed requests are retried up to three times with a delay of 600 ms between attempts.

Dynamically select policies

Additional extension methods exist which can be used to add Polly-based handlers. One such extension is AddPolicyHandler , which has multiple overloads. One overload allows the request to be inspected when defining which policy to apply:

In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. For any other HTTP method, a 30-second timeout is used.

Add multiple Polly handlers

It’s common to nest Polly policies to provide enhanced functionality:

In the preceding example, two handlers are added. The first uses the AddTransientHttpErrorPolicy extension to add a retry policy. Failed requests are retried up to three times. The second call to AddTransientHttpErrorPolicy adds a circuit breaker policy. Further external requests are blocked for 30 seconds if five failed attempts occur sequentially. Circuit breaker policies are stateful. All calls through this client share the same circuit state.

Add policies from the Polly registry

An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry . An extension method is provided which allows a handler to be added using a policy from the registry:

In the preceding code, two policies are registered when the PolicyRegistry is added to the ServiceCollection . To use a policy from the registry, the AddPolicyHandlerFromRegistry method is used, passing the name of the policy to apply.

Further information about IHttpClientFactory and Polly integrations can be found on the Polly wiki.

HttpClient and lifetime management

A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory . There’s an HttpMessageHandler per named client. The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn’t expired.

Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. Creating more handlers than necessary can result in connection delays. Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

The default handler lifetime is two minutes. The default value can be overridden on a per named client basis. To override it, call SetHandlerLifetime on the IHttpClientBuilder that is returned when creating the client:

Disposal of the client isn’t required. Disposal cancels outgoing requests and guarantees the given HttpClient instance can’t be used after calling Dispose. IHttpClientFactory tracks and disposes resources used by HttpClient instances. The HttpClient instances can generally be treated as .NET objects not requiring disposal.

Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory . This pattern becomes unnecessary after migrating to IHttpClientFactory .

Alternatives to IHttpClientFactory

Using IHttpClientFactory in a DI-enabled app avoids:

  • Resource exhaustion problems by pooling HttpMessageHandler instances.
  • Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • The SocketsHttpHandler shares connections across HttpClient instances. This sharing prevents socket exhaustion.
  • The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

Cookies

The pooled HttpMessageHandler instances results in CookieContainer objects being shared. Unanticipated CookieContainer object sharing often results in incorrect code. For apps that require cookies, consider either:

  • Disabling automatic cookie handling
  • Avoiding IHttpClientFactory

Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

Logging

Clients created via IHttpClientFactory record log messages for all requests. Enable the appropriate information level in your logging configuration to see the default log messages. Additional logging, such as the logging of request headers, is only included at trace level.

The log category used for each client includes the name of the client. A client named MyNamedClient, for example, logs messages with a category of System.Net.Http.HttpClient.MyNamedClient.LogicalHandler . Messages suffixed with LogicalHandler occur outside the request handler pipeline. On the request, messages are logged before any other handlers in the pipeline have processed it. On the response, messages are logged after any other pipeline handlers have received the response.

Logging also occurs inside the request handler pipeline. In the MyNamedClient example, those messages are logged against the log category System.Net.Http.HttpClient.MyNamedClient.ClientHandler . For the request, this occurs after all other handlers have run and immediately before the request is sent out on the network. On the response, this logging includes the state of the response before it passes back through the handler pipeline.

Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. This may include changes to request headers, for example, or to the response status code.

Including the name of the client in the log category enables log filtering for specific named clients where necessary.

Configure the HttpMessageHandler

It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

An IHttpClientBuilder is returned when adding named or typed clients. The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. The delegate is used to create and configure the primary HttpMessageHandler used by that client:

Use IHttpClientFactory in a console app

In a console app, add the following package references to the project:

In the following example:

  • IHttpClientFactory is registered in the Generic Host’s service container.
  • MyService creates a client factory instance from the service, which is used to create an HttpClient . HttpClient is used to retrieve a webpage.
  • The service’s GetPage method is executed to write the first 500 characters of the webpage content to the console. For more information on calling services from Program.Main , see Dependency injection in ASP.NET Core.

Header propagation middleware

Header propagation is a community supported middleware to propagate HTTP headers from the incoming request to the outgoing HTTP Client requests. To use header propagation:

Reference the community supported port of the package HeaderPropagation. ASP.NET Core 3.1 and later supports Microsoft.AspNetCore.HeaderPropagation.

Configure the middleware and HttpClient in Startup :

The client includes the configured headers on outbound requests:

Читайте также:  Zsh themes mac os
Оцените статью