Using custom negotiator and custom formatter in Web API 2
ProductFormatter  

The WriteToStreamAsync() method could be improved by using the using keyword with the StreamWriter. By using using the flushing and disposing of the stream is done automatically.  

Having a string.Format() like it is used together with the string.Join() is suboptimal. You should better add the product details to the List&lt;string&gt; and let the string.Join() to the work.  

Implementing this points will lead to  

public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
    List&lt;string&gt; productStrings = new List&lt;string&gt;();
    IEnumerable&lt;Product&gt; products = value is Product ? new Product[] { (Product)value } : (IEnumerable&lt;Product&gt;)value;
    foreach (Product product in products)
    {
        productStrings.Add(product.ProductID);
        productStrings.Add(product.Name);
        productStrings.Add(product.Price);
    } 

    using (StreamWriter writer = new StreamWriter(writeStream))
    {
        await writer.WriteAsync(string.Join(",", productStrings));
    }
}  


You should also take into account that object value could be null. Assuming that it is an IEnumerable&lt;Product&gt; if it isn't a Product type is suboptimal.  



CustomNegotiator  

The retrieving of the formatter should be extracted to a separate method otherwise the Negotiate() method would do to many things and would violate the single responsibility principle.  

The way how you are doing the search is ok, but could be improved by extracting the "application/x.product" to a class constant.  

This if 


if (request.Headers.UserAgent.Where(x =&gt; x.Product != null
&amp;&amp; x.Product.Name.ToLower().Equals("chrome")).Count() &gt; 0 &amp;&amp; formatter != null)  



should be improved by first checking if formatter != null and to use Any() instead of Count() &gt; 0. By using Count() &gt; 0 all of the items will be iterated over and counted. Using Any() will iterate only until any item is found for which the condition is true. Instead of x.Product.Name.ToLower().Equals("chrome") you should use the overloaded Equals() method which takes a StringComparision enum as second parameter. This buyes you two things. The first is the call to ToLower() isn't needed anymore and the second is that your code will pass the turkey test.  

Implementing this points will lead to  

private const string mediaType = "application/x.product";
private MediaTypeFormatter FindFormatter(IEnumerable&lt;MediaTypeFormatter&gt; formatters)
{

    foreach (var formatter in formatters)
    {
        var foundMediaType = formatter.SupportedMediaTypes.Where(mt =&gt; mt.MediaType == mediaType).SingleOrDefault();
        if (foundMediaType != null)
        {
            return formatter;
        }
    }

    return null;
}  

public class CustomNegotiator : DefaultContentNegotiator
{
    public override ContentNegotiationResult Negotiate(Type type,
                HttpRequestMessage request, IEnumerable&lt;MediaTypeFormatter&gt; formatters)
    {

        var formatter = FindFormatter(formatters);

        if (formatter != null &amp;&amp; request.Headers.UserAgent.Any(x =&gt; x.Product != null
                                 &amp;&amp; x.Product.Name.Equals("chrome", StringComparison.OrdinalIgnoreCase)))
        {
            // if client is chrome then return superproduct and use custom formatter for it
            return new ContentNegotiationResult(formatter,
                            new MediaTypeHeaderValue(mediaType));
        }
        else
        {
            return base.Negotiate(type, request, formatters);
        }
    }
}