我有一个简单的WCF REST服务:
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Service1
{
[WebGet(UriTemplate = "{id}")]
public SampleItem Get(string id)
{
return new SampleItem() { Id = Int32.Parse(id), StringValue = "Hello" };
}
}
服务应该返回的媒体没有限制。
当我发送指定json
格式的请求时,它返回JSON:
GET http://localhost/RestService/4 HTTP/1.1
User-Agent: Fiddler
Accept: application/json
Host: localhost
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 30
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sun, 02 Oct 2011 18:06:47 GMT
{"Id":4,"StringValue":"Hello"}
当我指定xml
时,它会返回XML:
GET http://localhost/RestService/4 HTTP/1.1
User-Agent: Fiddler
Accept: application/xml
Host: localhost
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 194
Content-Type: application/xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sun, 02 Oct 2011 18:06:35 GMT
<SampleItem xmlns="http://schemas.datacontract.org/2004/07/RestPrototype.Service" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Id>4</Id><StringValue>Hello</StringValue></SampleItem>
到目前为止,问题是该服务没有返回Vary
HTTP标头来说明内容已经协商并且Accept
http标头是决定性因素。
不应该是这样吗?:
GET http://localhost/RestService/4 HTTP/1.1
User-Agent: Fiddler
Accept: application/json
Host: localhost
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 30
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Vary:Accept
Date: Sun, 02 Oct 2011 18:06:47 GMT
{"Id":4,"StringValue":"Hello"}
据我所知,就缓存而言,“Vary”标头将告诉中间缓存,响应是根据URI和Accept
HTTP标头生成的。否则,代理可以缓存json响应,并将其用于要求xml的人。
有什么方法可以让WCF REST自动放置这个标题?
感谢。
答案 0 :(得分:4)
您可以使用自定义消息检查器将Vary
标头添加到响应中。基于automatic formatting rules for WCF WebHTTP,顺序为1)Accept header; 2)内容 - 请求消息的类型; 3)操作中的默认设置和4)行为本身的默认设置。只有前两个依赖于请求(从而影响Vary
标头),对于您的场景(缓存),只有GET很有趣,所以我们也可以丢弃传入的Content-Type。因此编写这样的检查器非常简单:如果设置了AutomaticFormatSelectionEnabled
属性,那么我们为所有GET请求的响应添加Vary: Accept
标头 - 下面的代码就是这样做的。如果要包含内容类型(对于非GET请求),您也可以修改检查器以查看传入请求。
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();
}
}
答案 1 :(得分:4)
在WCF Web API中,我们计划在连接期间添加自动设置Vary标头。目前,如果您使用的是Web API,则可以使用自定义操作处理程序或消息处理程序来完成此操作。对于WCF HTTP,然后使用消息检查器作为Carlos推荐的方法。
答案 2 :(得分:1)
似乎webHttpBinding的设计符合in this post描述的模型,该模型允许soap与非soap端点“共存”。该链接中代码的端点URL的含义是每个端点都将资源作为单个内容类型提供。该链接中的端点配置为通过endpointBehaviors属性支持soap,json和plain XML。
您的示例显示webHttpBinding可以支持内容协商,但由于WCF尚未生成Vary标头,因此它仅部分实现。如果您想更密切地使用包含REST架构风格的框架,请查看the reasons you might want to use OpenRasta.
答案 3 :(得分:1)
这种行为恕我直言,违反了http://tools.ietf.org/html/draft-ietf-httpbis-p6-cache-16#section-3.5中应该出现的行为。在协商回复的情况下,我看不到不发送Vary的任何理由。
我会将它发送到WCF HTTP列表进行澄清/修复,然后在这里回答。
扬