CountZero
CountZero

Reputation: 6379

Reading an HttpError result from HttpResponseMessage without using exceptions

I'm trying to pull an HttpError out of an HttpResponseMessage message which may or not be there. If the Api throws an exception it will be serialised as an HttpError however errors such as 404's will not be in this format.

I've managed to fix this bug in the code below by catching to exception thrown if we fail to deserialize the HttpError.

The issue is now I'm using exception driven development.

Idealy I want something like this.

var httpError = await response.Content.TryReadAsAsync<HttpError>(formatters);
if (httpError == null)
{
   // Definetly not an HttpError and no exception thrown
}

Surely the must be an easy way of telling the type of the content in the HttpContent?

public static async Task<ApiResponseMessage<T>> GetApiResponseAsync<T>(this HttpResponseMessage response, IEnumerable<MediaTypeFormatter> formatters) where T : class
        {    
            if (!response.IsSuccessStatusCode)
            {
                HttpError httpError;

                // Exception driven programming 
                try
                {
                    // Could use string?
                    var contentString = response.Content.ReadAsStringAsync();

                    // This doesn't work. Throws exception if not correct type
                    var contentObject = await response.Content.ReadAsAsync<object>();
                    var alwaysNull = contentObject as HttpError;

                    httpError = await response.Content.ReadAsAsync<HttpError>(formatters);
                }
                catch (Exception)
                {
                    httpError = null;
                }

                return new ApiResponseMessage<T>
                {
                    IsSuccess = false,
                    HttpError = httpError,
                    Response = response
                };
            }
            return new ApiResponseMessage<T>
            {
                IsSuccess = true,
                Result = await response.Content.ReadAsAsync<T>(formatters),
                Response = response
            };
        }

Upvotes: 1

Views: 2438

Answers (2)

Harald S. Hanssen
Harald S. Hanssen

Reputation: 494

Cleaned up the code so it at least compiles.

 public class ReadAsyncResult<T>
{
    public ReadAsyncResult()
    {
    }

    public ReadAsyncResult(T result)
    {
        Result = result;
        IsSuccess = result != null;
    }

    public T Result { get; set; }
    public bool IsSuccess { get; set; }

    public static async Task<ReadAsyncResult<T>> TryReadAsAsync<T>(HttpContent content)
    {
        return await TryReadAsAsync<T>(content, CancellationToken.None);
    }

    public static async Task<ReadAsyncResult<T>> TryReadAsAsync<T>(HttpContent content,
        CancellationToken cancellationToken)
    {
        if (content == null)
            return new ReadAsyncResult<T>();

        var type = typeof(T);

        var objectContent = content as ObjectContent;

        if (objectContent?.Value != null && type.IsInstanceOfType(objectContent.Value))
        {
            return new ReadAsyncResult<T>((T) objectContent.Value);
        }

        var mediaType = content.Headers.ContentType;
        var reader =
            new MediaTypeFormatterCollection(new MediaTypeFormatterCollection()).FindReader(type, mediaType);

        if (reader == null) return new ReadAsyncResult<T>();

        var value = await ReadAsAsyncCore<T>(content, type, reader, cancellationToken);
        return new ReadAsyncResult<T>(value);
    }

    private static async Task<T> ReadAsAsyncCore<T>(HttpContent content, Type type, MediaTypeFormatter formatter,
        CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();

        var stream = await content.ReadAsStreamAsync();
        var result = await formatter.ReadFromStreamAsync(type, stream, content, null, cancellationToken);

        return (T) result;
    }
}

Upvotes: 1

CountZero
CountZero

Reputation: 6379

It is of course, blindingly simple.

var message = new HttpResponseMessage();
HttpError httpError;
message.TryGetContentValue(out httpError);

if (httpError != null)
{
    // Do stuff
}

Edit:

This didn't fix my issue as the content type was not of type ObjectResult. I was expecting the TryGetContentValue to work in the same way as HttpContent.ReadAsAsync.

After digging through the source code for ReadAsAsync I have created a working solution.

   public class ReadAsyncResult<T>
        {
            public ReadAsyncResult()
            {
            }

            public ReadAsyncResult(T result)
            {
                Result = result;
                IsSuccess = result != null;
            }

            public T Result { get; set; }
            public bool IsSuccess { get; set; }
        }

        public static async Task<ReadAsyncResult<T>> TryReadAsAsync<T>(this HttpContent content)
        {
            return await TryReadAsAsync<T>(content, CancellationToken.None);
        }

        public static async Task<ReadAsyncResult<T>> TryReadAsAsync<T>(this HttpContent content, CancellationToken cancellationToken)
        {
            if (content == null)
                return new ReadAsyncResult<T>();

            var type = typeof(T);

            var objectContent = content as ObjectContent;

            if (objectContent?.Value != null && type.IsInstanceOfType(objectContent.Value))
            {
                return new ReadAsyncResult<T>((T)objectContent.Value);
            }

            var mediaType = content.Headers.ContentType;
            var reader = new MediaTypeFormatterCollection(new MediaTypeFormatterCollection()).FindReader(type, mediaType);

            if (reader == null) return new ReadAsyncResult<T>();

            var value = await ReadAsAsyncCore<T>(content, type, reader, cancellationToken);
            return new ReadAsyncResult<T>(value);
        }

        private static async Task<T> ReadAsAsyncCore<T>(HttpContent content, Type type, MediaTypeFormatter formatter, CancellationToken cancellationToken)
        {
             cancellationToken.ThrowIfCancellationRequested();

            var stream = await content.ReadAsStreamAsync();
            var result = await formatter.ReadFromStreamAsync(type, stream, content, null, cancellationToken);

            return (T) result;
        } 

Upvotes: 0

Related Questions