why is the parameter of my WCF Rest service method always null?....I do access the service's method and i do get the string returned by the wcf method, but the parameter remains null.
Operation Contract:
[OperationContract]
[WebInvoke(UriTemplate = "AddNewLocation",
Method="POST",
BodyStyle = WebMessageBodyStyle.WrappedRequest,
ResponseFormat = WebMessageFormat.Json,
RequestFormat = WebMessageFormat.Json)]
string AddNewLocation(NearByAttractions newLocation);
Implementation of AddNewLocation method
public string AddNewLocation(NearByAttractions newLocation)
{
if (newLocation == null)
{
//I'm always getting this text in my logfile
Log.Write("In add new location:- Is Null");
}
else
{
Log.Write("In add new location:- " );
}
//String is returned even though parameter is null
return "59";
}
Client code:
WebClient clientNewLocation = new WebClient();
clientNewLocation.Headers[HttpRequestHeader.ContentType] = "application/json";
JavaScriptSerializer js = new JavaScriptSerializer();
js.MaxJsonLength = Int32.MaxValue;
//Serialising location object to JSON
string serialLocation = js.Serialize(newLocation);
//uploading JSOn string and retrieve location's ID
string jsonLocationID = clientNewLocation.UploadString(GetURL() + "AddNewLocation", serialLocation);
I also tried this code in my client but still get a null parameter
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(NearByAttractions));
MemoryStream ms = new MemoryStream();
ser.WriteObject(ms, newLocation);
String json = Encoding.UTF8.GetString(ms.ToArray());
WebClient clientNewLocation = new WebClient();
clientNewLocation.Headers[HttpRequestHeader.ContentType] = "application/json";
string r = clientNewLocation.UploadString(GetURL() + "AddNewLocation", json);
Console.Write(r);
Then i also changed the BodyStyle option to "Bare" but then I got the following error (with both client codes):
The remote server returned an error: (400) Bad Request.
Any help please? thanks
Edit 1:
My GetUrl() method loads the web service IP address from the web config file and returns an object of type Uri
private static Uri GetURL()
{
Configuration config = WebConfigurationManager.OpenWebConfiguration("~/web.config");
string sURL = config.AppSettings.Settings["serviceURL"].Value;
Uri url = null;
try
{
url = new Uri(sURL);
}
catch (UriFormatException ufe)
{
Log.Write(ufe.Message);
}
catch (ArgumentNullException ane)
{
Log.Write(ane.Message);
}
catch (Exception ex)
{
Log.Write(ex.Message);
}
return url;
}
service address stored in web config as follows:
<appSettings>
<add key="serviceURL" value="http://192.168.2.123:55666/TTWebService.svc/"/>
</appSettings>
This is how my NearByAttraction class defined
[DataContractAttribute]
public class NearByAttractions
{
[DataMemberAttribute(Name = "ID")]
private int _ID;
public int ID
{
get { return _ID; }
set { _ID = value; }
}
[DataMemberAttribute(Name = "Latitude")]
private string _Latitude;
public string Latitude
{
get { return _Latitude; }
set { _Latitude = value; }
}
[DataMemberAttribute(Name = "Longitude")]
private string _Longitude;
public string Longitude
{
get { return _Longitude; }
set { _Longitude = value; }
}
You seem to be in the right track. You need the Bare body style, otherwise you'd need to wrap the serialized version of your input in another JSON object. The second code should work - but without more information about how the service is set up and what GetURL() returns we can only guess.
One way to find out what to send to a WCF REST service is to use a WCF client itself for that - using the WebChannelFactory<T> class, then use a tool such as Fiddler to see what it's sending. The example below is a SSCCE which shows your scenario working.
public class StackOverflow_15786448
{
[ServiceContract]
public interface ITest
{
[OperationContract]
[WebInvoke(UriTemplate = "AddNewLocation",
Method = "POST",
BodyStyle = WebMessageBodyStyle.Bare,
ResponseFormat = WebMessageFormat.Json,
RequestFormat = WebMessageFormat.Json)]
string AddNewLocation(NearByAttractions newLocation);
}
public class NearByAttractions
{
public double Lat { get; set; }
public double Lng { get; set; }
public string Name { get; set; }
}
public class Service : ITest
{
public string AddNewLocation(NearByAttractions newLocation)
{
if (newLocation == null)
{
//I'm always getting this text in my logfile
Console.WriteLine("In add new location:- Is Null");
}
else
{
Console.WriteLine("In add new location:- ");
}
//String is returned even though parameter is null
return "59";
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
host.Open();
Console.WriteLine("Host opened");
Console.WriteLine("Using WCF-based client (WebChannelFactory)");
var factory = new WebChannelFactory<ITest>(new Uri(baseAddress));
var proxy = factory.CreateChannel();
var newLocation = new NearByAttractions { Lat = 12, Lng = -34, Name = "56" };
Console.WriteLine(proxy.AddNewLocation(newLocation));
Console.WriteLine();
Console.WriteLine("Now with WebClient");
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(NearByAttractions));
MemoryStream ms = new MemoryStream();
ser.WriteObject(ms, newLocation);
String json = Encoding.UTF8.GetString(ms.ToArray());
WebClient clientNewLocation = new WebClient();
clientNewLocation.Headers[HttpRequestHeader.ContentType] = "application/json";
string r = clientNewLocation.UploadString(baseAddress + "/AddNewLocation", json);
Console.WriteLine(r);
}
}
Solved and thank you
I changed BodyStyle to "Bare" So my serivce interface is as follows:
[OperationContract]
[WebInvoke(UriTemplate = "AddNewLocation",
Method="POST",
BodyStyle = WebMessageBodyStyle.Bare,
ResponseFormat = WebMessageFormat.Json,
RequestFormat = WebMessageFormat.Json)]
string AddNewLocation(NearByAttractions newLocation);
Then implemented my client as follows:
MemoryStream ms = new MemoryStream();
DataContractJsonSerializer serialToUpload = new DataContractJsonSerializer(typeof(NearByAttractions));
serialToUpload.WriteObject(ms, newLocation);
WebClient client = new WebClient();
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
client.UploadData(GetURL() + "AddNewLocation", "POST", ms.ToArray());
I used WebClient.UploadData instead of UploadString.
Related
I have a self hosted WCF service for handling HTTP POST request.
My WCF service method will handle 2 types of request (1. getting data, 2. Do some action with given data).
My scenario :
1. Started Action request with large amount of data. : during the starting of that action do some processing.
2. client reads some results.
3. sending the remaining data for the previous action.
I implemented this using 'TcpListener' it is working.
Problem:
when i try to implement this scenario using WCF, i have got the 'Bad request' during the first action request.
[ServiceContract]
public interface ITest
{
[OperationContract, WebInvoke(Method = "POST", UriTemplate = "{*pathname}")]
Stream GetResponse(string pathname, Stream requestBody);
}
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Exact, ConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.Single, UseSynchronizationContext = false, IncludeExceptionDetailInFaults = true)]
class TestService : ITest
{
public Uri BaseUri { get; private set; }
public IPAddress Address { get; private set; }
public Int32 Port { get; private set; }
public bool IsStarted { get { return Host != null; } }
public ServiceHost Host { get; private set; }
public TestService()
{
}
public TestService(IPAddress ipAddress, int port)
{
if (ipAddress == null) { throw new ArgumentNullException("Address"); }
this.Address = ipAddress;
this.Port = port;
this.BaseUri = new Uri(string.Format("http://{0}:{1}", this.Address, this.Port));
}
public void Start()
{
if (IsStarted) { throw new InvalidOperationException("Service is already started."); }
Host = CreateServiceHost();
Host.Open();
}
public void Stop()
{
if (!IsStarted) { throw new InvalidOperationException("Service is already stopped."); }
Host.Close();
Host = null;
}
private ServiceHost CreateServiceHost()
{
ServiceHost host = new ServiceHost(typeof(TestService), this.BaseUri);
WebHttpBinding webhttpBinding = new WebHttpBinding();
webhttpBinding.MaxBufferPoolSize = int.MaxValue;
webhttpBinding.MaxBufferSize = int.MaxValue;
webhttpBinding.MaxReceivedMessageSize = int.MaxValue;
CustomBinding binding = new CustomBinding(webhttpBinding);
WebMessageEncodingBindingElement webEncoding = binding.Elements.Find<WebMessageEncodingBindingElement>();
webEncoding.ContentTypeMapper = new RawMapper();
host.AddServiceEndpoint(typeof(ITest), binding, new Uri(this.BaseUri.AbsoluteUri)).Behaviors.Add(new WebHttpBehavior());
return host;
}
public Stream GetResponse(string pathname, Stream requestBody)
{
Stream response=null;
/************** Process 'requestBody' **************/
//requestBody contains "MY_ACTION"
//{
// Find the start delimeter from response : START processing
// Find the end delimeter from response : STOP processing
//}
//requestBody contains "GET_MY_DATA"
//{
// Find data and send response
//}
return response;
}
}
public class RawMapper : WebContentTypeMapper
{
public override WebContentFormat GetMessageFormatForContentType(string contentType)
{
return WebContentFormat.Raw;
}
}
Any help/guidance would be great as I'm really stuck on this.
I'm trying to use WCF to consume a 3rd-party REST service which responds with URL-encoded data:
a=1&b=2&c=3
I have this now:
[DataContract]
class Response {
[DataMember(Name="a")]
public int A { get;set;}
[DataMember(Name="b")]
public int B { get;set;}
[DataMember(Name="c")]
public int C { get;set;}
}
[ServiceContract]
interface IService
{
[OperationContract]
Response Foo();
}
But it comes back with:
There was an error checking start element of object of type Response. The data at the root level is invalid. Line 1, position 1.
WCF does not "understand" forms-data content type (application/x-www-forms-urlencoded), so it won't be able to read that response directly. You can either implement a message formatter which will be able to convert that format into your contract, or you can receive the response as a Stream (which will give you the full bytes of the response), which you can decode into your class.
The code below shows how you could implement a formatter for this operation. It's not generic, but you should get the picture of what needs to be done.
public class StackOverflow_16493746
{
[ServiceContract]
public class Service
{
[WebGet(UriTemplate = "*")]
public Stream GetData()
{
WebOperationContext.Current.OutgoingResponse.ContentType = "application/x-www-form-urlencoded";
return new MemoryStream(Encoding.UTF8.GetBytes("a=1&b=2&c=3"));
}
}
[ServiceContract]
interface IService
{
[WebGet]
Response Foo();
}
[DataContract]
class Response
{
[DataMember(Name = "a")]
public int A { get; set; }
[DataMember(Name = "b")]
public int B { get; set; }
[DataMember(Name = "c")]
public int C { get; set; }
}
public class MyResponseFormatter : IClientMessageFormatter
{
private IClientMessageFormatter originalFormatter;
public MyResponseFormatter(IClientMessageFormatter originalFormatter)
{
this.originalFormatter = originalFormatter;
}
public object DeserializeReply(Message message, object[] parameters)
{
HttpResponseMessageProperty httpResp = (HttpResponseMessageProperty)
message.Properties[HttpResponseMessageProperty.Name];
if (httpResp.Headers[HttpResponseHeader.ContentType] == "application/x-www-form-urlencoded")
{
if (parameters.Length > 0)
{
throw new InvalidOperationException("out/ref parameters not supported in this formatter");
}
byte[] bodyBytes = message.GetReaderAtBodyContents().ReadElementContentAsBase64();
NameValueCollection pairs = HttpUtility.ParseQueryString(Encoding.UTF8.GetString(bodyBytes));
Response result = new Response();
foreach (var key in pairs.AllKeys)
{
string value = pairs[key];
switch (key)
{
case "a":
result.A = int.Parse(value);
break;
case "b":
result.B = int.Parse(value);
break;
case "c":
result.C = int.Parse(value);
break;
}
}
return result;
}
else
{
return this.originalFormatter.DeserializeReply(message, parameters);
}
}
public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
{
throw new NotSupportedException("This is a reply-only formatter");
}
}
public class MyClientBehavior : WebHttpBehavior
{
protected override IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
{
return new MyResponseFormatter(base.GetReplyClientFormatter(operationDescription, endpoint));
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<IService> factory = new ChannelFactory<IService>(new WebHttpBinding(), new EndpointAddress(baseAddress));
factory.Endpoint.Behaviors.Add(new MyClientBehavior());
IService proxy = factory.CreateChannel();
Response resp = proxy.Foo();
Console.WriteLine("a={0},b={1},c={2}", resp.A, resp.B, resp.C);
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
When I call manually wcf service what should I type in url place :
HttpWebRequest httpWebRequest = WebRequest.Create(url)as HttpWebRequest;
should be there url to my svc file
http://localhost/service/LMTService.svc
or wsdl
http://localhost/service/LMTService.svc?wsdl
or url to service action ?
http://localhost/service/LMTService.svc/soap/GetSerializedSoapData
It depends on the binding of the endpoint. If using a SOAP binding (i.e., basicHttpBinding, wsHttpBinding, etc), the request URI should be the endpoint address (not the service address). Also, in some SOAP versions (such as SOAP11, used in basicHttpBinding), you need to specify the action as a HTTP header. If you're using webHttpBinding (with webHttp behavior) the address is the address of the endpoint, plus the UriTemplate (which by default is just the method name) of the operation you want to call.
The code below shows a HttpWebRequest-based request being sent to two endpoints, a one using BasicHttpBinding, one using WebHttpBinding.
public class StackOverflow_7525850
{
[ServiceContract]
public interface ITest
{
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest)]
int Add(int x, int y);
}
public class Service : ITest
{
public int Add(int x, int y)
{
return x + y;
}
}
public static string SendRequest(string uri, string method, string contentType, string body, Dictionary<string, string> headers)
{
string responseBody = null;
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
req.Method = method;
if (headers != null)
{
foreach (string headerName in headers.Keys)
{
req.Headers[headerName] = headers[headerName];
}
}
if (!String.IsNullOrEmpty(contentType))
{
req.ContentType = contentType;
}
if (body != null)
{
byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
req.GetRequestStream().Write(bodyBytes, 0, bodyBytes.Length);
req.GetRequestStream().Close();
}
HttpWebResponse resp;
try
{
resp = (HttpWebResponse)req.GetResponse();
}
catch (WebException e)
{
resp = (HttpWebResponse)e.Response;
}
if (resp == null)
{
responseBody = null;
Console.WriteLine("Response is null");
}
else
{
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();
if (respStream != null)
{
responseBody = new StreamReader(respStream).ReadToEnd();
Console.WriteLine(responseBody);
}
else
{
Console.WriteLine("HttpWebResponse.GetResponseStream returned null");
}
}
Console.WriteLine();
Console.WriteLine(" *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* ");
Console.WriteLine();
return responseBody;
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "basic");
host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "web").Behaviors.Add(new WebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
string soapBody = #"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body>
<Add xmlns=""http://tempuri.org/"">
<x>44</x>
<y>55</y>
</Add>
</s:Body>
</s:Envelope>";
SendRequest(baseAddress + "/basic", "POST", "text/xml", soapBody, new Dictionary<string, string> { { "SOAPAction", "http://tempuri.org/ITest/Add" } });
SendRequest(baseAddress + "/web/Add", "POST", "text/xml", "<Add xmlns=\"http://tempuri.org/\"><x>55</x><y>66</y></Add>", null);
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
When I have that service:
[OperationContract]
ResponseMessage GetData(RequestMessage message);
Where
class RequestMessage
{
public string data
}
class ResponseMessage
{
public string data
}
and call this service
string data2 = ""
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost/Service.svc/GetData");
request.ContentType = "application/json";
request.Method = "POST";
request.KeepAlive = true;
using (Stream requestStream = request.GetRequestStream())
{
var bytes = Encoding.UTF8.GetBytes(data2);
requestStream.Write(bytes, 0, bytes.Length);
requestStream.Close();
}
var response = (HttpWebResponse)request.GetResponse();
var abc = new StreamReader(response.GetResponseStream()).ReadToEnd();
as data2 should I send string "mydata" or should I wrap it in json format : {"message": {"data":"mydata"}}
??
I have problem with understand how should be send data on client side by post to get it properly on service side :/
You didn't mention how the service is defined. Assuming your endpoint uses webHttpBinding, and an endpoint behavior with <webHttp/> with default values, then the default value for the body style is "Bare", which means that the request should contain only the serialized version of the parameter. For this case, you can send the string {"data":"hello world"}.
If you want a quick way to find what's the expected format for a WCF service, you can use a WCF client, using the same contract / binding / behaviors, and send a message to the server (and capture it on fiddler). For example, the code below shows a server similar to yours, and a client which sends a request to it.
public class StackOverflow_7492678
{
public class RequestMessage
{
public string data;
}
public class ResponseMessage
{
public string data;
}
[ServiceContract]
public interface ITest
{
[OperationContract]
ResponseMessage GetData(RequestMessage message);
}
public class Service : ITest
{
public ResponseMessage GetData(RequestMessage message)
{
return new ResponseMessage { data = message.data };
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
var endpoint = host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "");
endpoint.Behaviors.Add(new WebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new WebHttpBinding(), new EndpointAddress(baseAddress));
factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
ITest proxy = factory.CreateChannel();
Console.WriteLine(proxy.GetData(new RequestMessage { data = "mydata" }).data);
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
In my RESTful WCF Serice I need to pass a class as a parameter for URITemplate.
I was able to pass a string or multiple strings as parameters.
But I have a lot of fields are there to pass to WCF Service.
So I have created a class and added all the fields as properties and then
I want to pass this class as one paramenter to the URITemplate.
When I am trying to pass class to the URITemplate I am getting error
"Path segment must have type string". Its not accepting class as a parameter.
Any idea how to pass class as a parameter.
Here is my code (inputData is class)
[OperationContract]
[WebGet(UriTemplate = "/InsertData/{param1}")]
string saveData(inputData param1);
You actually can pass a complex type (class) in a GET request, but you need to "teach" WCF how to use it, via a QueryStringConverter. However, you usually shouldn't do that, especially in a method which will change something in the service (GET should be for read-only operations).
The code below shows both passing a complex type in a GET (with a custom QueryStringConverter) and POST (the way it's supposed to be done).
public class StackOverflow_6783264
{
public class InputData
{
public string FirstName;
public string LastName;
}
[ServiceContract]
public interface ITest
{
[OperationContract]
[WebGet(UriTemplate = "/InsertData?param1={param1}")]
string saveDataGet(InputData param1);
[OperationContract]
[WebInvoke(UriTemplate = "/InsertData")]
string saveDataPost(InputData param1);
}
public class Service : ITest
{
public string saveDataGet(InputData param1)
{
return "Via GET: " + param1.FirstName + " " + param1.LastName;
}
public string saveDataPost(InputData param1)
{
return "Via POST: " + param1.FirstName + " " + param1.LastName;
}
}
public class MyQueryStringConverter : QueryStringConverter
{
public override bool CanConvert(Type type)
{
return (type == typeof(InputData)) || base.CanConvert(type);
}
public override object ConvertStringToValue(string parameter, Type parameterType)
{
if (parameterType == typeof(InputData))
{
string[] parts = parameter.Split(',');
return new InputData { FirstName = parts[0], LastName = parts[1] };
}
else
{
return base.ConvertStringToValue(parameter, parameterType);
}
}
}
public class MyWebHttpBehavior : WebHttpBehavior
{
protected override QueryStringConverter GetQueryStringConverter(OperationDescription operationDescription)
{
return new MyQueryStringConverter();
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "").Behaviors.Add(new MyWebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
WebClient client = new WebClient();
Console.WriteLine(client.DownloadString(baseAddress + "/InsertData?param1=John,Doe"));
client = new WebClient();
client.Headers[HttpRequestHeader.ContentType] = "application/json";
Console.WriteLine(client.UploadString(baseAddress + "/InsertData", "{\"FirstName\":\"John\",\"LastName\":\"Doe\"}"));
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
Passing a class (data contract) is only possible with POST or PUT request (WebInvoke). GET request allows only simple types where each must be part of UriTemplate to be mapped to parameter in the method.