如何访问 WCF REST 服务中的 HTTP POST 请求正文?
这是服务定义:
[ServiceContract]
public interface ITestService
{
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "EntryPoint")]
MyData GetData();
}
这是实现:
public MyData GetData()
{
return new MyData();
}
我想使用以下代码来访问 HTTP 请求:
IncomingWebRequestContext context = WebOperationContext.Current.IncomingRequest;
但是 IncomingWebRequestContext 只提供对标头的访问,而不提供对正文的访问。
谢谢。
我认为最好的方法不涉及WebOperationContext
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "EntryPoint", BodyStyle = WebMessageBodyStyle.Bare)]
MyData GetData(System.IO.Stream pStream);
使用
OperationContext.Current.RequestContext.RequestMessage
很抱歉回答晚了,但我想我应该添加与 UriTemplate 参数一起使用的内容来获取请求正文。
[ServiceContract]
public class Service
{
[OperationContract]
[WebInvoke(UriTemplate = "{param0}/{param1}", Method = "POST")]
public Stream TestPost(string param0, string param1)
{
string body = Encoding.UTF8.GetString(OperationContext.Current.RequestContext.RequestMessage.GetBody<byte[]>());
return ...;
}
}
body
被分配来自消息正文的原始字节的字符串。
此代码返回正文。需要使用
System
、System.Text
、System.Reflection
、System.ServiceModel
public string GetBody()
{
var requestMessage = OperationContext.Current.RequestContext.RequestMessage;
var messageDataProperty = requestMessage.GetType().GetProperty("MessageData", (BindingFlags)0x1FFFFFF);
var messageData = messageDataProperty.GetValue(requestMessage);
var bufferProperty = messageData.GetType().GetProperty("Buffer");
var buffer = bufferProperty.GetValue(messageData) as ArraySegment<byte>?;
var body = Encoding.UTF8.GetString(buffer.Value.Array);
return body;
}
我能够通过在该线程上拼凑多个答案来解决我的问题。我想做的是在 POST 正文中接收 JSON 有效负载,并且不对它执行任何操作,以便我可以按照我的意愿解析它。这对我们很重要,因为传入的 JSON 不是单个预定的东西,而是几种可能的东西之一。是的,我们可以为每个新事物添加单独的调用,但我们正在尝试允许系统在不更改代码的情况下进行扩展。
在之前的尝试中,我只能在内容类型为“text/plain”的情况下才能使其正常工作,但后来我却在解释为什么它不能作为“application/json”发送有人想称呼它。
所以...从本页的答案...以下签名:
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "test/", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
void TestCall();
然后从正文中获取 JSON,如下所示:
private string GetJSONFromBody()
{
string json = "";
string contentType = WebOperationContext.Current.IncomingRequest.ContentType;
if (contentType.Contains("application/json"))
{
var requestMessage = OperationContext.Current.RequestContext.RequestMessage;
var messageDataProperty = requestMessage.GetType().GetProperty("MessageData", (BindingFlags)0x1FFFFFF);
var messageData = messageDataProperty.GetValue(requestMessage);
var bufferProperty = messageData.GetType().GetProperty("Buffer");
var buffer = bufferProperty.GetValue(messageData) as ArraySegment<byte>?;
json = Encoding.UTF8.GetString(buffer.Value.Array);
}
else if (contentType.Contains("text"))
{
json = Encoding.UTF8.GetString(OperationContext.Current.RequestContext.RequestMessage.GetBody<byte[]>());
}
return json;
}
这样,有人尝试发送 JSON,它就会起作用,但最后我能够支持“application/json”。我仍然需要支持“text/plain”,因为已经有应用程序以这种方式调用。
上述答案帮助我想出了这个解决方案。我收到带有名称/值对的 json。 {“p1”:7514,“p2”:3412,“p3”:“乔·史密斯”...}
[OperationBehavior(Impersonation = ImpersonationOption.Allowed)]
[WebInvoke(Method = "POST",
BodyStyle = WebMessageBodyStyle.Bare,
RequestFormat = WebMessageFormat.Json
)]
public Stream getJsonRequest()
{
// Get the raw json POST content. .Net has this in XML string..
string JSONstring = OperationContext.Current.RequestContext.RequestMessage.ToString();
// Parse the XML string into a XML document
XmlDocument doc = new XmlDocument();
doc.LoadXml(JSONstring);
foreach (XmlNode node in doc.DocumentElement.ChildNodes)
{
node.Name // has key
node.InnerText; // has value
似乎因为 WCF 被设计为与传输协议无关,所以默认情况下服务方法不提供对 HTTP 特定信息的访问。然而,我刚刚发现了一篇描述“ASP.Net 兼容模式”的好文章,它本质上允许您指定您的服务确实旨在通过 HTTP 公开。
将
aspNetCompatibilityEnabled
配置添加到 Web.config
,并结合 AspNetCompatibilityRequirements
属性到所需的服务操作,应该可以解决问题。我自己也想尝试一下。
浩彬
我对之前的答案表示歉意,我愚蠢地认为我刚刚投射了 WebOperationContext 来获取 OperationContext,不幸的是,真正的答案要丑陋得多。
让我先说一下,一定有更好的方法!
首先,我创建了自己的上下文对象,它可以附加到现有的 OperationContext 对象。
public class TMRequestContext : IExtension<OperationContext> {
private OperationContext _Owner;
public void Attach(OperationContext owner) {
_Owner = owner;
}
public void Detach(OperationContext owner) {
_Owner = null;
}
public static TMRequestContext Current {
get {
if (OperationContext.Current != null) {
return OperationContext.Current.Extensions.Find<TMRequestContext>();
} else {
return null;
}
}
}
}
为了能够访问这个新的上下文对象,您需要将其添加为当前上下文对象的扩展。我通过创建一个消息检查器类来做到这一点。
public class TMMessageInspector : IDispatchMessageInspector {
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) {
OperationContext.Current.Extensions.Add(new TMRequestContext());
return null;
}
}
为了使消息检查器正常工作,您需要创建一个新的“行为”。我使用以下代码完成了此操作。
public class TMServerBehavior : IServiceBehavior {
public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) {
//Do nothing
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) {
foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers) {
foreach (EndpointDispatcher epDisp in chDisp.Endpoints) {
epDisp.DispatchRuntime.MessageInspectors.Add(new TMMessageInspector());
}
}
}
}
您应该能够在配置文件中添加行为,尽管我是通过创建新主机并在 OnOpening 方法中手动添加行为对象来实现的。我最终使用这些类的目的不仅仅是访问 OperationContext 对象。我用它们来记录和覆盖错误处理以及访问 http 请求对象等。所以,这并不是看起来那么荒谬的解决方案。几乎,但不完全!
我真的不记得为什么我不能直接访问OperationContext.Current。我隐约记得它总是空的,而这个令人讨厌的过程是我获得实际包含有效数据的实例的唯一方法。
这就是我所做的:
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Web;
using System;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text;
namespace YourSpaceName
{
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class YourClassName
{
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "YourMethodName({id})", BodyStyle = WebMessageBodyStyle.Bare)]
public Stream YourMethodName(Stream input, string id)
{
WebOperationContext ctx = WebOperationContext.Current;
ctx.OutgoingResponse.Headers.Add("Content-Type", "application/json");
string response = $@"{{""status"": ""failure"", ""message"": ""Please specify the Id of the vehicle requisition to retrieve."", ""d"":null}}";
try
{
string response = (new StreamReader(input)).ReadToEnd();
}
catch (Exception ecp)
{
response = $@"{{""status"": ""failure"", ""message"": ""{ecp.Message}"", ""d"":null}}";
}
return new MemoryStream(Encoding.UTF8.GetBytes(response));
}
}
}
此代码只是读取输入并将其写出。 无论变量名称如何,POST 请求的正文都会自动分配给输入。如您所见,您的 UriTemplate 中仍然可以包含变量。
除了缓冲区没有完全填满请求正文并且访问内部数组缓冲区会提供旧数据的用例外,上面的答案几乎已经存在。
public string GetBody()
{
var requestMessage = OperationContext.Current.RequestContext.RequestMessage;
var messageDataProperty = requestMessage.GetType().GetProperty("MessageData", (BindingFlags)0x1FFFFFF);
var messageData = messageDataProperty.GetValue(requestMessage);
var bufferProperty = messageData.GetType().GetProperty("Buffer");
var buffer = bufferProperty.GetValue(messageData) as IEnumerable<byte>;
var body = Encoding.UTF8.GetString(buffer.ToArray());
return body;
}
我尝试进行编辑,但仍在审核中,不希望它丢失。