diff --git a/src/Carter/Response/ResponseExtensions.cs b/src/Carter/Response/ResponseExtensions.cs index 4a59a5f..90986f0 100644 --- a/src/Carter/Response/ResponseExtensions.cs +++ b/src/Carter/Response/ResponseExtensions.cs @@ -26,8 +26,8 @@ public static Task Negotiate(this HttpResponse response, T model, Cancellatio { var negotiators = response.HttpContext.RequestServices .GetServices() - .ToList(); - + .ToArray(); + var chosenNegotiator = SelectNegotiator(response.HttpContext, negotiators); return chosenNegotiator.Handle(response.HttpContext.Request, response, model, cancellationToken); @@ -44,10 +44,10 @@ public static Task AsJson(this HttpResponse response, T model, CancellationTo { var negotiators = response.HttpContext.RequestServices .GetServices() - .ToList(); - - var negotiator = negotiators.First(x => x.GetType() == typeof(DefaultJsonResponseNegotiator)); + .ToArray(); + var negotiator = FindDefaultJsonNegotiator(negotiators); + return negotiator.Handle(response.HttpContext.Request, response, model, cancellationToken); } @@ -56,33 +56,67 @@ public static Task AsJson(this HttpResponse response, T model, CancellationTo /// or defaults to if none match. /// /// Current - /// List of available instances + /// Array of available instances /// The selected for the response. - private static IResponseNegotiator SelectNegotiator(HttpContext httpContext, List negotiators) + private static IResponseNegotiator SelectNegotiator(HttpContext httpContext, IResponseNegotiator[] negotiators) { - IResponseNegotiator negotiator = null; + MediaTypeHeaderValue.TryParseList(httpContext.Request.Headers["Accept"], out var accepts); - MediaTypeHeaderValue.TryParseList(httpContext.Request.Headers["Accept"], out var accept); - if (accept != null) + if (accepts?.Count > 0) { - var ordered = accept.OrderByDescending(x => x.Quality ?? 1); + StableSortByQualityDescending(accepts); - foreach (var acceptHeader in ordered) + foreach (var acceptHeader in accepts) { - negotiator = negotiators.FirstOrDefault(x => x.CanHandle(acceptHeader)); - if (negotiator != null) + foreach (var negotiator in negotiators) { - break; + if (negotiator.CanHandle(acceptHeader)) + { + return negotiator; + } } } } - if (negotiator == null) + return FindDefaultJsonNegotiator(negotiators); + } + + /// + /// Finds the registered . + /// + /// Thrown when no is registered. + private static IResponseNegotiator FindDefaultJsonNegotiator(IResponseNegotiator[] negotiators) + { + foreach (var negotiator in negotiators) { - negotiator = negotiators.First(x => x.GetType() == typeof(DefaultJsonResponseNegotiator)); + if (negotiator is DefaultJsonResponseNegotiator) + { + return negotiator; + } } - return negotiator; + throw new InvalidOperationException("DefaultJsonResponseNegotiator is not registered."); + } + + /// + /// Sorts media types by descending quality while preserving the order of equal-quality values. + /// + private static void StableSortByQualityDescending(IList accepts) + { + for (var i = 1; i < accepts.Count; i++) + { + var current = accepts[i]; + var currentQuality = current.Quality ?? 1; + + var j = i - 1; + while (j >= 0 && (accepts[j].Quality ?? 1) < currentQuality) + { + accepts[j + 1] = accepts[j]; + j--; + } + + accepts[j + 1] = current; + } } ///