0

When using convention based routing I am able to use a DelegatingHandler to create a response wrapper by overriding the SendAsync method.

        DelegatingHandler[] handler = new DelegatingHandler[] {
            new ResponseWrapper()
        };
        var routeHandler = HttpClientFactory.CreatePipeline(new HttpControllerDispatcher(config), handler);

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}",
            defaults: null,
            constraints: null,
            handler: routeHandler
        );

However, this approach does not work for methods that rely upon attribute routing. In my case convention based routing will not work for all scenarios and the routeHandler does not apply to the attribute based routes.

How can I apply a response wrapper to all attribute based route responses?

Zoop
  • 872
  • 3
  • 11
  • 24
  • 1
    Convention-based routing won't work for all scenarios? How is that possible being that attribute routing is a subset of convention-based routing functionality? – NightOwl888 Jun 21 '17 at 13:37
  • 1
    In your case, routeHandler is just controller action wrapper. Why do you not use ActionFilter which does the same, but much simpler and works with any route mechanism. – Ivan R. Jun 21 '17 at 15:02
  • @NightOwl888 UsersController GET operations api/users api/users/{userId} api/users/{userId}/enrollments api/users/{userId}/enrollments/{enrollmentId} Perhaps I am just being ignorant... is there a way to achieve the above using convention based routing? I don't mind creating multiple conventions but if there would need to be a convention based route for each controller it seems like attribute routing would be preferred. – Zoop Jun 21 '17 at 17:17

1 Answers1

0

I was able to add a global message handler that applies to all requests.

 config.MessageHandlers.Add(new ResponseWrapper());

Since I am using swagger, I also had to ignore the swagger request URI. Here is the code for the ResponseWrapper class in the event it helps someone. I have not had a chance to go back through it so there are certain to be some improvements...

public class ResponseWrapper : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var response = await base.SendAsync(request, cancellationToken);

            if (request.RequestUri.ToString().Contains("swagger"))
            {
                return response;
            }

            return BuildApiResponse(request, response);
        }

        private static HttpResponseMessage BuildApiResponse(HttpRequestMessage request, HttpResponseMessage response)
        {
            object content = null;
            string errorMessage = null;
            response.TryGetContentValue(out content);

            if (!response.IsSuccessStatusCode)
            {
                content = null;
                var error = new HttpError(response.Content.ReadAsStringAsync().Result);              
                var data = (JObject)JsonConvert.DeserializeObject(error.Message);
                errorMessage = data["message"].Value<string>();

                if (!string.IsNullOrEmpty(error.ExceptionMessage) && string.IsNullOrEmpty(errorMessage))
                {
                    errorMessage = error.ExceptionMessage;
                }
            }

            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;
        }
    }
Zoop
  • 872
  • 3
  • 11
  • 24