Skip to content

Instantly share code, notes, and snippets.

@HenrikFrystykNielsen
Created July 2, 2012 18:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save HenrikFrystykNielsen/3034715 to your computer and use it in GitHub Desktop.
Save HenrikFrystykNielsen/3034715 to your computer and use it in GitHub Desktop.
Sample showing a DelegatingHandler which plugs in a special HttpContent wrapper for saving response content to local disk and perform asynchronous post-processing on that file. This allows content from a response to be post-processed, for example to send
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace ResponseEntityProcessor.Handlers
{
/// <summary>
/// Wraps an inner <see cref="HttpContent"/> and forces the content to be written
/// both to a local file as well as to the output stream when SerializeToStreamAsync
/// is called.
/// </summary>
internal class HttpContentProcessor : HttpContentWrapper
{
private const int BufferSize = 8 * 1024;
private string _outputPath;
private Func<string, Task> _contentProcessor;
public HttpContentProcessor(HttpContent innerContent, string outputPath, Func<string, Task> contentProcessor)
: base(innerContent)
{
if (outputPath == null)
{
throw new ArgumentNullException("outputPath");
}
if (contentProcessor == null)
{
throw new ArgumentNullException("contentProcessor");
}
_outputPath = outputPath;
_contentProcessor = contentProcessor;
}
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
// First write out content to local file asynchronously
string fileName = Guid.NewGuid().ToString("D");
string filePath = Path.Combine(_outputPath, fileName);
using (FileStream fStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, BufferSize, useAsync: true))
{
await InnerContent.CopyToAsync(fStream);
}
// Now write out content to actual output stream
await InnerContent.CopyToAsync(stream);
// Invoke content processor so that it can process the content asynchonously
try
{
await _contentProcessor(filePath);
}
finally
{
// We are done with the local file and can delete it.
File.Delete(filePath);
}
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace ResponseEntityProcessor.Handlers
{
/// <summary>
/// Helper class for wrapping an inner <see cref="HttpContent"/> for the purpose of processing the data.
/// </summary>
public abstract class HttpContentWrapper : HttpContent
{
protected HttpContentWrapper(HttpContent innerContent)
{
if (innerContent == null)
{
throw new ArgumentNullException("innertContent");
}
InnerContent = innerContent;
// Copy headers from inner to outer content
foreach (KeyValuePair<string, IEnumerable<string>> header in InnerContent.Headers)
{
Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
protected HttpContent InnerContent { get; private set; }
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
return InnerContent.CopyToAsync(stream);
}
protected override bool TryComputeLength(out long length)
{
long? contentLength = InnerContent.Headers.ContentLength;
if (contentLength.HasValue)
{
length = contentLength.Value;
return true;
}
length = -1;
return false;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
InnerContent.Dispose();
}
}
}
}
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ResponseEntityProcessor.Handlers
{
/// <summary>
/// This <see cref="DelegatingHandler"/> checks that there is a response entity
/// and if so plugs in a <see cref="HttpContentProcessor"/> instance which
/// copies the response entity to a local file and then enables post-processing
/// on that file.
/// </summary>
public class ResponseEntityHandler : DelegatingHandler
{
private string _outputPath;
public ResponseEntityHandler(string outputPath)
{
if (outputPath == null)
{
throw new ArgumentNullException("outputPath");
}
_outputPath = outputPath;
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Wait for the response
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
// Check if there is any content then plug in our content processor wrapper
if (response.Content != null)
{
// By plugging in a HttpContentProcessor we can pass a Func<string, Task>
// which gets the file name of the local file and then processes it asynchronously.
response.Content = new HttpContentProcessor(response.Content, _outputPath,
(fileName) =>
{
// For this sample we only check the local file and then return.
// However, here you can do any work you like asynchronously.
FileInfo fileInfo = new FileInfo(fileName);
System.Console.WriteLine("Local file info: Name: {0}, Size: {1}", fileInfo.Name, fileInfo.Length);
return Task.FromResult(true);
});
}
return response;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment