When using the Microsoft Graph Client Library for .NET, you can face a situation that requires reading and editing either a response message sent to the Graph API or a response received from the Graph API.
The GraphServiceClient
allows you to add a custom message handler and handle all outgoing requests and incoming responses.
Message handlers
A message handler is a class that receives an HTTP request and returns an HTTP response.
A series of message handlers can be chained together.
- the first handler receives an HTTP request
- does some processing
- gives the request to the next handler
At some point, the response is created and goes back up the chain.
On the client side, the HttpClient
class uses a message handler to process requests.
Default handlers in the Graph API SDK
The GraphServiceClient
internally creates a new instance of HttpClient
with a default set of handlers.
The collection of default handlers contains the following items:
CompressionHandler
- handles compression
- adds
Accept-encoding: gzip
header to incoming request - decompresses response content when
Content-Encoding: gzip
header is present
RetryHandler
- retries sending the request
- handles HTTP statuses:
503
- service unavailable504
- gateway timeout429
- too many requests
RedirectHandler
- handles redirection of requests
- sends request to a new address defined in
Location
header - handles HTTP statuses:
301
- move permanently302
- found303
- see other307
- temporary redirect308
- permanent redirect
ParametersNameDecodingHandler
- decodes special characters in the request query parameters that had to be encoded due to RFC 6570 restrictions names before executing the request
UserAgentHandler
- appends the current library version to the
User-Agent
header
- appends the current library version to the
HeadersInspectionHandler
- allows the developer to inspect the headers of the request and response
GraphTelemetryHandler
- adds some telemetry data:
- version of the SDK library
- host current platform identifier
- host platform architecture
- host .NET installation
client-request-id
header with unique GUID of each request to help Microsoft investigate any errors that may occur
- adds some telemetry data:
Custom handlers
To create a custom message handler, create a class that inherits from System.Net.Http.DelegatingHandler
and override SendAsync
method.
In the implementation of the SendAsync
method:
- process the request message
- call
base.SendAsync
to send the request to the inner handler and to ensure that the request is forwarded through the request pipeline - the inner handler returns a response message
- process the response and return it to the caller
Create custom handler
I will create a custom handler to log the method and absolute path, including the query of a request, to the console. After a response is received, I will log the status code of the response and the client-request-id
header in case the response is not successful.
Let's start with the custom message handler:
public class ConsoleLoggingHandler : DelegatingHandler
{
private readonly ILogger _logger;
public ConsoleLoggingHandler(ILogger logger)
{
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
_logger.LogInformation($">>> {request.Method} {request.RequestUri.PathAndQuery}");
// send request through the pipeline
var response = await base.SendAsync(request, cancellationToken);
// check response status code
if (response.IsSuccessStatusCode)
{
_logger.LogInformation($"<<< {request.Method} {request.RequestUri.PathAndQuery}: {response.StatusCode}");
}
else
{
// log client-request-id header
var requestId = response.Headers.GetValues(CoreConstants.Headers.ClientRequestId).FirstOrDefault();
_logger.LogError($"<<< {request.Method} {request.RequestUri.PathAndQuery}: {response.StatusCode}, {CoreConstants.Headers.ClientRequestId}: {requestId}");
}
return response;
}
}
The instance of logger is passed through the constructor of the class.
In the next step, use the GraphClientFactory
class to create a default set of middleware for calling the Graph API:
var handlers = GraphClientFactory.CreateDefaultHandlers();
Create a console logger (NuGet Microsoft.Extensions.Logging.Console
):
var loggerFactory = LoggerFactory.Create(x => x.AddSimpleConsole(opt =>
{
opt.TimestampFormat = "s";
opt.UseUtcTimestamp = true;
opt.SingleLine = true;
}));
var logger = loggerFactory.CreateLogger<Program>();
Create a new instance of the ConsoleLoggingHandler
and add it to the collection of default handlers:
handlers.Add(new ConsoleLoggingHandler(logger));
Create a new instance of the HttpClient
configured with the provided handlers:
var httpClient = GraphClientFactory.Create(handlers);
Create a new instance of the GraphServiceClient
with configured HttpClient
:
var authenticationProvider = new BaseBearerTokenAuthenticationProvider(new TokenProvider());
var graphServiceClient = new GraphServiceClient(httpClient, authenticationProvider);
Call several endpoints and check the output in the console:
await graphServiceClient.Me.GetAsync();
await graphServiceClient.Me.Drive.GetAsync();
await graphServiceClient.Admin.ServiceAnnouncement.HealthOverviews.GetAsync();
The whole code of the console application:
static async Task Main(string[] args)
{
var handlers = GraphClientFactory.CreateDefaultHandlers();
var loggerFactory = LoggerFactory.Create(x => x.AddSimpleConsole(opt =>
{
opt.TimestampFormat = "s";
opt.UseUtcTimestamp = true;
opt.SingleLine = true;
}));
var logger = loggerFactory.CreateLogger<Program>();
handlers.Add(new ConsoleLoggingHandler(logger));
var httpClient = GraphClientFactory.Create(handlers);
var authenticationProvider = new BaseBearerTokenAuthenticationProvider(new TokenProvider());
var graphServiceClient = new GraphServiceClient(httpClient, authenticationProvider);
await graphServiceClient.Me.GetAsync();
await graphServiceClient.Me.Drive.GetAsync();
await graphServiceClient.Admin.ServiceAnnouncement.HealthOverviews.GetAsync();
}
The output:
Conclusion
I've explained what a message handler is, what a default set of message handlers used by the GraphServiceClient
is and I've demonstrated how easy it is to create a new delegating handler and integrate it with the GraphServiceClient
.