You can use a custom message inspector to add the Vary
header to the responses. Based on the automatic formatting rules for WCF WebHTTP, the order is 1) Accept header; 2) Content-Type of request message; 3) default setting in the operation and 4) default setting in the behavior itself. Only the first two are dependent on the request (thus influencing the Vary
header), and for your scenario (caching), only GET are interesting, so we can discard the incoming Content-Type as well. So writing such an inspector is fairly simple: if the AutomaticFormatSelectionEnabled
property is set, then we add the Vary: Accept
header for the responses of all GET requests - the code below does that. If you want to include the content-type (for non-GET requests as well), you can modify the inspector to look at the incoming request as well.
public class Post_0acbfef2_16a3_440a_88d6_e0d7fcf90a8e
{
[DataContract(Name = "Person", Namespace = "")]
public class Person
{
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
}
[ServiceContract]
public class MyContentNegoService
{
[WebGet(ResponseFormat = WebMessageFormat.Xml)]
public Person ResponseFormatXml()
{
return new Person { Name = "John Doe", Age = 33 };
}
[WebGet(ResponseFormat = WebMessageFormat.Json)]
public Person ResponseFormatJson()
{
return new Person { Name = "John Doe", Age = 33 };
}
[WebGet]
public Person ContentNegotiated()
{
return new Person { Name = "John Doe", Age = 33 };
}
[WebInvoke]
public Person ContentNegotiatedPost(Person person)
{
return person;
}
}
class MyVaryAddingInspector : IEndpointBehavior, IDispatchMessageInspector
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
WebHttpBehavior webBehavior = endpoint.Behaviors.Find<WebHttpBehavior>();
if (webBehavior != null && webBehavior.AutomaticFormatSelectionEnabled)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
}
}
public void Validate(ServiceEndpoint endpoint)
{
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
HttpRequestMessageProperty prop;
prop = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
if (prop.Method == "GET")
{
// we shouldn't cache non-GET requests, so only returning this for such requests
return "Accept";
}
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
string varyHeader = correlationState as string;
if (varyHeader != null)
{
HttpResponseMessageProperty prop;
prop = reply.Properties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;
if (prop != null)
{
prop.Headers[HttpResponseHeader.Vary] = varyHeader;
}
}
}
}
public static void SendGetRequest(string uri, string acceptHeader)
{
SendRequest(uri, "GET", null, null, acceptHeader);
}
public static void SendRequest(string uri, string method, string contentType, string body, string acceptHeader)
{
Console.Write("{0} request to {1}", method, uri.Substring(uri.LastIndexOf('/')));
if (contentType != null)
{
Console.Write(" with Content-Type:{0}", contentType);
}
if (acceptHeader == null)
{
Console.WriteLine(" (no Accept header)");
}
else
{
Console.WriteLine(" (with Accept: {0})", acceptHeader);
}
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
req.Method = method;
if (contentType != null)
{
req.ContentType = contentType;
Stream reqStream = req.GetRequestStream();
byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
reqStream.Write(bodyBytes, 0, bodyBytes.Length);
reqStream.Close();
}
if (acceptHeader != null)
{
req.Accept = acceptHeader;
}
HttpWebResponse resp;
try
{
resp = (HttpWebResponse)req.GetResponse();
}
catch (WebException e)
{
resp = (HttpWebResponse)e.Response;
}
Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);
foreach (string headerName in resp.Headers.AllKeys)
{
Console.WriteLine("{0}: {1}", headerName, resp.Headers[headerName]);
}
Console.WriteLine();
Stream respStream = resp.GetResponseStream();
Console.WriteLine(new StreamReader(respStream).ReadToEnd());
Console.WriteLine();
Console.WriteLine(" *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* ");
Console.WriteLine();
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(MyContentNegoService), new Uri(baseAddress));
ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(MyContentNegoService), new WebHttpBinding(), "");
endpoint.Behaviors.Add(new WebHttpBehavior { AutomaticFormatSelectionEnabled = true });
endpoint.Behaviors.Add(new MyVaryAddingInspector());
host.Open();
Console.WriteLine("Host opened");
foreach (string operation in new string[] { "ResponseFormatJson", "ResponseFormatXml", "ContentNegotiated" })
{
foreach (string acceptHeader in new string[] { null, "application/json", "text/xml", "text/json" })
{
SendGetRequest(baseAddress + "/" + operation, acceptHeader);
}
}
Console.WriteLine("Sending some POST requests with content-nego (but no Vary in response)");
string jsonBody = "{\"Name\":\"John Doe\",\"Age\":33}";
SendRequest(baseAddress + "/ContentNegotiatedPost", "POST", "text/json", jsonBody, "text/xml");
SendRequest(baseAddress + "/ContentNegotiatedPost", "POST", "text/json", jsonBody, "text/json");
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}