Monday 2 December 2019

Global Response Wrapper for Web API


When working with ASP.Net Web API, it is important to return a consistent response for all the requests that are processed by your API regardless of success or failure.
When retrieving data with a GET, your response body will contain data. This may be an array for multiple records (e.g. GET /products) or a single object (e.g. GET /products/42). A POST may not return a response body at all and instead use an HTTP Header to point the user to the newly created resource. If your service needs to return an error then the in-built error handling will return an object with a message property exposing the error message. In each of these cases, we are returning very different data to the client. Depending on your client, this can make it very difficult to consume your service. 

Delegating handlers are extremely useful for situations like above. They hook into the very early and very late stages of the request-response pipeline making them ideal for manipulating the response right before it is sent back to the client. 

Implementation:

Step 1: Create a Webapi project.

Step 2: Create a generic class for response.

Example:

namespace Core.Model.Common
{
    using System.Net;
    using System.Runtime.Serialization;
    /// <summary>
    /// APi global response wrapper template
    /// </summary>
    [DataContract]
    public class ApiResponse
    {
        /// <summary>
        /// Version number
        /// </summary>
        [DataMember]
        public string Version { get { return "1.0.0"; } }
        /// <summary>
        /// Status code
        /// </summary>
        [DataMember]
        public int StatusCode { get; set; }
        /// <summary>
        /// Error message
        /// </summary>
        [DataMember(EmitDefaultValue = false)]
        public string ErrorMessage { get; set; }
        /// <summary>
        /// Result
        /// </summary>
        [DataMember(EmitDefaultValue = false)]
        public object Result { get; set; }
        /// <summary>
        /// The Response constructor
        /// </summary>
        /// <param name="statusCode"></param>
        /// <param name="result"></param>
        /// <param name="errorMessage"></param>
        public ApiResponse(HttpStatusCode statusCode, object result = null, string errorMessage = null)
        {
            StatusCode = (int)statusCode;
            Result = result;
            ErrorMessage = errorMessage;
        }
    }
}

Step 3: Create a message handler and override SendAsync method.


    public class GlobalResponseWrappingHandler : DelegatingHandler
    {
        /// <summary>
        /// Send async overiride for message handler
        /// </summary>
        /// <param name="request"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            //Bypass if request is from swagger ui
            if (request.RequestUri.AbsolutePath.ToLower().Contains("swagger"))
            {
                return await base.SendAsync(request, cancellationToken);
            }
            else
            {
                var response = await base.SendAsync(request, cancellationToken);

                return BuildApiResponse(request, response);
            }
        }
        /// <summary>
        /// Build api response with custom response class
        /// </summary>
        /// <param name="request"></param>
        /// <param name="response"></param>
        /// <returns></returns>
        private static HttpResponseMessage BuildApiResponse(HttpRequestMessage request, HttpResponseMessage response)
        {
            object content;
            string errorMessage = null;

            if (response.TryGetContentValue(out content) && !response.IsSuccessStatusCode)
            {
                HttpError error = content as HttpError;

                if (error != null)
                {
                    content = null;
                    errorMessage = error.Message;

#if DEBUG
                    errorMessage = string.Concat(errorMessage, error.ExceptionMessage, error.StackTrace);
#endif
                }
            }

            var newResponse = request.CreateResponse(response.StatusCode, new ApiResponse(response.StatusCode, content, errorMessage));

            foreach (var header in response.Headers)
            {
                newResponse.Headers.Add(header.Key, header.Value);
            }

            return newResponse;
        }

    }

Step 4: Register the message handler in webapi config

var config = GlobalConfiguration.Configuration;
            config.MessageHandlers.Add(new GlobalResponseWrappingHandler());


Step 5: Add the below lines in Application_Start


var json = config.Formatters.JsonFormatter;
            json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
            config.Formatters.Remove(config.Formatters.XmlFormatter);

Step 6: Run the project.

No comments:

Post a Comment