在WCF RESTful服务中访问请求体

21

如何在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只提供了对头部的访问,而没有访问正文的方式。

谢谢。

9个回答

11

我认为最好的方式不涉及WebOperationContext

[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "EntryPoint", BodyStyle = WebMessageBodyStyle.Bare)]
MyData GetData(System.IO.Stream pStream);

BodyStyle 默认为 WebMessageBodyStyle.Bare - Ε Г И І И О
即使urltemplate有参数,这也可以工作。对我来说,这适用于原始的xml post请求,但使用OperationContext.Current.RequestContext.RequestMessage.ToString()的解决方案不起作用(结果为“...stream...”)。 - SalientBrain

9

使用

OperationContext.Current.RequestContext.RequestMessage


9
它会提供给你消息的XML而不是POST请求正文。 - Vadym Chekan

8

抱歉回复晚了,但我想补充一下使用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是从消息体的原始字节分配给一个字符串。


8

这段代码返回正文文本。需要使用SystemSystem.TextSystem.ReflectionSystem.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;
}

这正是我所需要的。如果可以的话,我会给你20个赞!非常感谢! - John Fairbanks
有没有想过通过带注释的参数来实现这个功能?就像WebApi中的[FromBody]一样。 - 0x777
这个在我们把代码移到生产环境后就出问题了,那里的流量要大得多。不幸的是,请求的正文字符串中包含了来自这个WCF请求以及其他方法的其他WCF请求的部分。可能是某种缓冲区溢出。 - undefined

2
我能够在本主题的多个答案中拼凑解决了我的问题。 我想做的是在POST请求的正文中接收JSON有效负载,并且不对其进行任何处理,以便我可以随意解析它。 这对我们很重要,因为传入的JSON不是单个预定的内容,而是可能有几种。 是的,我们可以为每个新事物添加单独的调用,但我们正在尝试允许系统在没有代码更改的情况下具有可扩展性。
在之前的尝试中,我只能在内容类型为“text / plain”时使其工作,但是当有人想要调用它时,我就坐在那里咀嚼着自己的舌头,解释为什么它不能作为“application / json”发送。
所以...从这个页面上的答案中...以下签名:
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "test/", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
void TestCall();

然后从body中获取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'。

1
以上的回答帮助我想出了这个解决方案。我正在接收包含名称/值对的JSON数据。{"p1":7514,"p2":3412, "p3":"joe smith" ... }

[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

1
对于 XML POST 请求,它给了我一个“...stream...” 的结果。即使使用 Encoding.UTF8.GetString(OperationContext.Current.RequestContext.RequestMessage.GetBody<byte[]>()) 也无法解决问题。对我来说,[Kasthor] 的解决方案或以下方法可行:var inputStream = OperationContext.Current.RequestContext.RequestMessage.GetBody<Stream>(); var sr = new StreamReader(inputStream, Encoding.UTF8); var str = sr.ReadToEnd(); - SalientBrain
1
你可以不必遍历节点,而是从文档中创建一个 XmlNodeReader,然后将其传递给 DataContractJsonSerializer.ReadObject(),该方法接受一个 XmlReader。这样,非字符串类型的值(例如数字和布尔值)就可以正确地转换为相应的类型! - Stefan Paul Noack

1

看起来由于WCF旨在成为传输协议无关的,因此默认情况下服务方法不提供对HTTP特定信息的访问。然而,我刚刚发现了一篇很好的文章,描述了“ASP.Net兼容模式”,它基本上允许您指定您的服务确实打算通过HTTP公开。

链接

aspNetCompatibilityEnabled配置添加到Web.config中,并将AspNetCompatibilityRequirements属性添加到所需的服务操作中,应该就可以解决问题了。我即将尝试这个方法。

Haw-Bin


真的,但它会削弱服务的自托管能力。 - Ε Г И І И О

0

对于之前的回答,我深感抱歉。我愚蠢地认为只需将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。我模糊地记得它总是为空,而这个麻烦的过程是我能够获取包含有效数据的实例的唯一方式。


嗨Darrel,我尝试了你的建议,遇到了一些问题。 当我使用你的代码时,我在编译时得到了这个错误: 无法将类型'System.ServiceModel.Web.WebOperationContext'转换为'System.ServiceModel.OperationContext'而当我将代码更改为: string body = OperationContext.Current.RequestContext.RequestMessage.ToString();运行时,body是一个空字符串。有什么想法吗?谢谢, Uri - urini

0
这是我做的事情:
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请求的主体自动分配给“input”,而不考虑变量名称。正如您所看到的,您仍然可以在UriTemplate中使用变量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接