How do I access the HTTP POST request body in a WCF REST service?
Here is the service definition:
[ServiceContract]
public interface ITestService
{
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "EntryPoint")]
MyData GetData();
}
Here is the implementation:
public MyData GetData()
{
return new MyData();
}
I though of using the following code to access the HTTP request:
IncomingWebRequestContext context = WebOperationContext.Current.IncomingRequest;
But the IncomingWebRequestContext only gives access to the headers, not the body.
Thanks.
Best way i think doesn't involve WebOperationContext
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "EntryPoint", BodyStyle = WebMessageBodyStyle.Bare)]
MyData GetData(System.IO.Stream pStream);
Use
OperationContext.Current.RequestContext.RequestMessage
Sorry for the late answer but I thought I would add what works with UriTemplate parameters to get the request body.
[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 is assigned a string from the raw bytes of the message body.
This code return body text. Need using 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;
}
I was able to solve my issue cobbling together multiple answers on this thread. What I am trying to do is to receive a JSON payload in the body of the POST and not have anything done to it so that I can parse it as I will. This is important to us because the JSON coming in is not a single predetermined thing, but rather one of several possible. Yes, we could add a separate call for each new thing, but we are trying to allow the system to be extensible without code changes.
In previous attempts I have only been able to get this to work if the content type was 'text/plain', but then I'm sitting chewing on my tongue explaining why it can't be sent as 'application/json' when someone wants to call it.
So... from the answers on this page... the following signature:
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "test/", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
void TestCall();
and then getting the JSON from the body like so:
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;
}
This way however someone tries to send the JSON it will work, but at last I was able to support 'application/json'. I still needed to support 'text/plain' since there are already apps calling that way.
The above answers helped me come up with this solution. I am receiving json with name/value pairs. {"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
It seems that because WCF is designed to be transport protocol-agnostic, a service method doesn't provide access to HTTP-specific information by default. However, I just came across a nice article describing "ASP.Net Compatibility Mode" which essentially allows you to specify that your service is indeed intended to be exposed via HTTP.
Link
Adding the aspNetCompatibilityEnabled configuration to Web.config, combined with the AspNetCompatibilityRequirements attribute to the desired service operations, should do the trick. I'm about to try this myself.
Haw-Bin
My apologies for the previous answer, I stupidly assumed that I had just cast WebOperationContext to get at the OperationContext, unfortunately the real answer is much more ugly.
Let me preface this with, there must be a better way!
First I created my own context object, that could be attached to the existing OperationContext object.
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;
}
}
}
}
In order to be able to access this new context object, you need to add it as an extension to the current one. I did that by creating a message inspector class.
public class TMMessageInspector : IDispatchMessageInspector {
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) {
OperationContext.Current.Extensions.Add(new TMRequestContext());
return null;
}
}
In order for the message inspector to work you need to create a new "behaviour". I did this using the following code.
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());
}
}
}
}
The behaviour you should be able to add in the config file, although I did it by creating a new host and adding the behaviour object manually in the OnOpening method. I ended up using these class for much more than just accessing the OperationContext object. I used them for logging and overriding the error handling and access to the http request object,etc. So, it is not quite as ridiculous solution as it seems. Almost, but not quite!
I really don't remember why I could not just access OperationContext.Current directly. I have a faint recollection that it was always empty and this nasty process was the only way I could get an instance that actually contained valid data.
Here is what I did:
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));
}
}
}
This code simply reads the input and writes it out.
the body of the POST request is automatically assigned to input irrespect of the variable name. As you can see, you can still have variables in your your UriTemplate.
Related
I have got a WCF service from one of my users. I want to check whether the service is working or not without adding any proxy. Is there any way that I can achieve this in my C# code?
You may achieve this by implementing an endpoint at WCF and querying it from the client.
Following is the WCF code I would use.
// Used for communication between WCF and client. Must be implemented both WCF and client sides
public class Response {
public int Id { get; set; }
public string Data { get; set; }
}
// Web Service - Interface
[ServiceContract]
public interface IService
{
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json,
UriTemplate = "Up")]
string CheckLogin();
}
// Web service - Implementation
public class ServiceImplementation : IService
{
public Response isUp()
{
Response response = new Response();
response.ID = 200;
response.Data = "web service is up";
return response;
}
}
Following is the client method to test if the service is up.
public bool CheckIfUp(string encodedUrl)
{
WebRequest request;
WebResponse ws;
Response response = new Response();
string url = "http://servicePath/isUp"; // your wcf url
try
{
request = WebRequest.Create(url);
ws = request.GetResponse();
return (response.ID == 200);
}
catch (Exception e)
{
Console.Write(e.StackTrace);
}
return false;
}
Hope this helps.
Try appending ?wsdl at the of the URL pointing to the WCF-service.
If your Web service address is
http://services.aonaware.com/DictService/DictService.asmx
you can reach your wsdl file like this:
http://services.aonaware.com/DictService/DictService.asmx?WSDL
The returned WSDL allows you to see all the method the WCF-service provides.
I'm having trouble with implementing URL redirection using message inspectors for my WCF REST service. The idea is to implement AfterReceiveRequest to change the incoming request to effectively do nothing, and then in BeforeSendReply, make call to another URI which provides the actual service. You can think of this scenario as something where we do not want to break clients, so they continue to use old URIs for invocations, but internally we redirect them to a different URI.
For example, this is what my service contract looks like:
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebGet(UriTemplate = "person/{person}")]
string GreetMe(string person);
[OperationContract]
[WebGet(UriTemplate = "donothing")]
string DoNothing();
}
And this is the implementation:
public class Service1 : IService1
{
public string GreetMe(string person)
{
return string.Format("Hi {0}", person);
}
public string DoNothing()
{
return string.Empty;
}
}
Then I'm adding a MessageInspector to endpoint behavior. The AfterReceiveRequest of this inspector is like this:
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
OperationContext operationContext = OperationContext.Current;
if (WebOperationContext.Current != null && WebOperationContext.Current.IncomingRequest.UriTemplateMatch != null)
{
UriBuilder baseUriBuilder = new UriBuilder(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri);
UriBuilder requestUriBuilder = new UriBuilder(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri);
OperationContext.Current.IncomingMessageProperties["MicrosoftDataServicesRootUri"] = baseUriBuilder.Uri.ToString();
OperationContext.Current.IncomingMessageProperties["MicrosoftDataServicesRequestUri"] = baseUriBuilder.Uri.ToString() + "donothing";
OperationContext.Current.IncomingMessageHeaders.To = new Uri(baseUriBuilder.Uri.ToString() + "donothing");
operationContext.IncomingMessageProperties["Via"] = new Uri(baseUriBuilder.Uri.ToString() + "donothing");
request.Headers.To = new Uri(baseUriBuilder.Uri.ToString() + "donothing");
}
return null;
}
Basically, I want to change all incoming requests to go to DoNothing() operation. However, when I run this, I still see that the request makes it to GreetMe method. I attached the debugger and can see that my code in AfterReceiveRequest is getting executed, but I'm not sure why the URI redirection does not happen.
Any ideas?
I'm trying to understand how to use WCF Data Services (based on EF 4.1) to create a restful web service that will persist entities passed as JSON objects.
I've been able to create a method that can accept a GET request with a set of primitive data types as arguments. I don't like that solution, I would prefer to send a POST request with a JSON object in the http request body.
I've found that I can't get the framework to serialize the json into an object for me, but i would be fine with doing it manually.
My problem is that I can't seem to read the body of the POST request - the body should be the JSON payload.
Here's a rough crack at it below. I've tried a few different iterations of this and can't seem to get the raw JSON out of the request body.
Any thoughts? A better way to do this? I just want to POST some JSON data and process it.
[WebInvoke(Method = "POST")]
public void SaveMyObj()
{
StreamReader r = new StreamReader(HttpContext.Current.Request.InputStream);
string jsonBody = r.ReadToEnd(); // jsonBody is empty!!
JavaScriptSerializer jss = new JavaScriptSerializer();
MyObj o = (MyObj)jss.Deserialize(jsonBody, typeof(MyObj));
// Now do validation, business logic, and persist my object
}
My DataService is an Entity Framework DataService that extends
System.Data.Services.DataService<T>
If I try adding non-primitive values as parameters to the method, i see the following exception in the trace log:
System.InvalidOperationException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
'Void SaveMyObj(MyNamespace.MyObj)' has a parameter 'MyNamespace.MyObj o' of type 'MyNamespace.MyObj' which is not supported for service operations. Only primitive types are supported as parameters.
Add parameters to your method. You'll also want some additional attributes on your WebInvoke.
Here's an example (from memory so it might be a little off)
[WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, UriTemplate = "modifyMyPerson")]
public void Modify(Person person) {
...
}
With person class something like this:
[DataContract]
public class Person {
[DataMember(Order = 0)]
public string FirstName { get; set; }
}
And json sent like this
var person = {FirstName: "Anthony"};
var jsonString = JSON.stringify({person: person});
// Then send this string in post using whatever, I personally use jQuery
EDIT: This is using "wrapped" approach. Without wrapped approach you would take out the BodyStyle = ... and to stringify the JSON you would just do JSON.stringify(person). I just usually use the wrapped methodology in case I ever need to add additional parameters.
EDIT For full code sample
Global.asax
using System;
using System.ServiceModel.Activation;
using System.Web;
using System.Web.Routing;
namespace MyNamespace
{
public class Global : HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.Add(new ServiceRoute("myservice", new WebServiceHostFactory(), typeof(MyService)));
}
}
}
Service.cs
using System;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
namespace MyNamespace
{
[ServiceContract]
[ServiceBehavior(MaxItemsInObjectGraph = int.MaxValue)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MyService
{
[OperationContract]
[WebInvoke(UriTemplate = "addObject", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
public void AddObject(MyObject myObject)
{
// ...
}
[OperationContract]
[WebInvoke(UriTemplate = "updateObject", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
public void UpdateObject(MyObject myObject)
{
// ...
}
[OperationContract]
[WebInvoke(UriTemplate = "deleteObject", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
public void DeleteObject(Guid myObjectId)
{
// ...
}
}
}
And add this to Web.config
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
When I used REST to call my WCF service it skips the method being called with the following error:
The response body is a byte stream.
See the service documentation for
allowed content types.
I am dealing with placing objects in memory and then trying to serialize them as a JSON string. To do this, I have an interface to my service contract:
[ServiceContract]
public interface IDashboardService
{
[WebGet(UriTemplate = "dashboard/group/id/{id}", ResponseFormat=WebMessageFormat.Json)]
[OperationContract]
Stream GetGroupById(string id);
}
And in my service contract overrides the method that is being skipped:
public class Dashboard : GroupBase, Contracts.IDashboardService
{
public Dashboard() : base()
{
if (!ServiceSecurityContext.Current.PrimaryIdentity.IsAuthenticated)
throw new WebException("Unauthorized: Class: Dashboard, Method: Dashboard()",
System.Net.HttpStatusCode.Forbidden);
}
public override System.IO.Stream GetGroupById(string id)
{
return base.GetGroupById(id);
}
}
Here is the base method:
public virtual Stream GetGroupById(string id)
{
byte[] bytes = null;
var groupId = System.Convert.ToInt32(id);
var serializer = new JavaScriptSerializer();
switch (groupId)
{
case 0: // regions
var regions = GroupRepository.GetAllRegions();
bytes = Encoding.UTF8.GetBytes(serializer.Serialize(regions));
break;
case 1: // customers
var customers = CustomerRepository.GetAllCustomers();
bytes = Encoding.UTF8.GetBytes(serializer.Serialize(customers));
break;
}
return new MemoryStream(bytes);
}
Any idea how I can get this working? Thanks.
You can't return a stream and then tell WCF to format it as JSON. Just remove the MessageFormat attribute and set the Content Type header manually to application/Json
I had to put this line in before returning the memory stream to get it to format as JSON:
WebOperationContext.Current.OutgoingResponse.ContentType = "application/json; charset=utf-8";
What is the best practice for multiple WCF end point types? i.e.: JSON, JSONP, SOAP & POX? I am using WCF 3.5.
For instance, I have the following JSONP web service:
namespace RivWorks.Web.Service.JSONP
{
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceContract(Name = "Negotiate", Namespace = "http://rivworks.com/Services/2009/01/15")]
public class Negotiate //: svcContracts.INegotiateService
{
#region Constructors
public NegotiateService() { }
#endregion
#region Methods
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json)]
[JSONPBehavior(callback = "method")]
public dto.NegotiateSetup GetSetup(string method, string jsonInput)
{ … }
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json)]
[JSONPBehavior(callback = "method")]
public List<dto.NegotiateSetup> GetSetupAll(string method, string jsonInput)
{ … }
#endregion
}
}
Now, I need to expose a SOAP and POX version. My initial thought was to create a WCF Application Project for each service type endpoint. Then publish each one to an App directory under the main website.
RivWorks.Web.Service.SOAP -> http://rivworks.com/services/soap
RivWorks.Web.Service.POX -> http://rivworks.com/services/pox
RivWorks.Web.Service.JSON -> http://rivworks.com/services/json
RivWorks.Web.Service.JSONP -> http://rivworks.com/services/jsonp
I have an internal “Bus” that the working code lives in. I just want to wrap that bus code with service endpoints of various types. What is the best practice way to do this?
PS: Is there a tool for merging the WCF web.config(s) into the main site’s web.config?
TIA
-kb
UPDATE:
How do you go about handling the different decorators that can be applied to a Method signature. i.e. - compare the following Methods (which are identical) and the decorators associated with them:
Decorator for a POX endpoint:
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Xml)]
public bool ValidateUser(string UserName, string Password)
{
...
}
vs
Decorator for a JSON endpoint:
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json)]
public bool ValidateUser(string UserName, string Password)
{
...
}
And here is another example where the method signature itself changes:
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json)]
[JSONPBehavior(callback = "method")]
public dto.NegotiateSetup GetSetup(string method, string jsonInput)
{
// Deserialize the input and get all the data we need...
JObject o = Newtonsoft.Json.Linq.JObject.Parse(jsonInput);
string urlRef = String.Format("{0}", o["ref"]).Drop("\"");
string clientDate = String.Format("{0}", o["dt"]).Drop("\"");
string stringProductID = String.Format("({0})", o["productId"]).Drop("\"").Drop("(").Drop(")");
string SKU = String.Format("{0}", o["sku"]).Drop("\"");
string env = String.Format("{0}", o["env"]).Drop("\"");
string stringCompanyID = String.Format("({0})", o["CompanyId"]).Drop("\"").Drop("(").Drop(")");
string boolPortalFlag = String.Format("({0})", o["PortalFlag"]).Drop("\"").Drop("(").Drop(")");
...
}
vs a POX endpoint type:
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Xml)]
public dto.NegotiateSetup GetSetup(string urlRef, string clientDate, string stringProductID, string SKU, string env, string stringCompanyID, string boolPortalFlag)
{
...
}
Why would you want a separate project? WCF can expose many services on many endpoints, just in a single project.
Simply add the additional endpoints as .svc files. Do whatever you need to do with routing if you're sensitive to the ".svc" at the end (though remember that humans don't see the .svc).