I'm having issues with strings containing ampersands in my client generated from dotnet-svcutil.
When I recieve a response through the client all ampersands are serialized as
<Users xsi:type=\"xsd:string\">John & Jane</Users>
When i extract the value from the request in my code it has been deserialized as an ampersand
UserResponse users = await GetUsersAsync()
users.Names = "John & Jane"
In a later stage i need to send the value of the users back like this
await SetUsersAsync(users.Names);
When i intercept the generated soap request I can see that the ampersand has not been escaped
public class MyMessageInspector : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
var xml = reply.ToString(); // This shows <Users xsi:type=\"xsd:string\">John & Jane</Users>
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
var xml = request.ToString(); // This shows <Users xsi:type=\"xsd:string\">John & Jane</Users>
return null;
}
}
This causes the whole request to fail in the connected service. From the information I've found online the ampersand should be converted to & by the serializer. I could convert the value myself, but since I'm only returning a value I got from the soap client, I would have to check and convert every single string that is returned back.
Do you have any suggestions on how I could configure the generated client to automatically escape ampersands?
For .net core 2.1+:
Use the HtmlEncode method of HttpUtility available in System.Web to encode the strings you send to the client.
using System.Web;
await SetUsersAsync(HttpUtility.HtmlEncode(users.Names));
For .net core 2.0 (or .net 1.* with a package install):
use HtmlEncoder.Default.Encode(users.Names)
which is available in System.Text.Encodings.Web
Related
I have a JAX-RS #POST endpoint whose input data has to be #Valid:
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response myEndpoint(#javax.validation.Valid MyInputData input) { /*...*/ }
With MyInputData class annotated with many constraints:
#lombok.Data
public class InputData {
#Size(min = 1, max = 3)
private String someString;
/* ... */
}
Beyond that I have an ExceptionMapper<ConstraintViolationException> that transform the Exception into a Collection<String> (basically every single ConstraintViolation transformed to String using its getMesssage() method), then returns a Response.status(Status.BAD_REQUEST).entity(list).build().
Everything is working nicely. Fed an invalid input and I get back a HTTP 400 with a nice array of constraint violations in json format.
So far, so good...
BUT... the messages are in server's locale. Even if HTTP post sends a Accept-language header (and it is correctly detected when getting HttpServletRequest::getLocale).
By the time the ExceptionMapper gets hold of ConstraintViolation every message has already been interpolated, so no chance set the client locale.
Since the validation runs even before the JAX-RS resource (indeed, the JAX-RS resource isn't even called in case of invalid input), this locale aware message interpolator must be configured somewhere else.
Where? Is there already a MessageInterpolator implementation whose operation takes the HttpServletRequest locale into account?
I'm using RestSharp, and using Json.NET for serialization (see here).
Json.NET supports BSON, and since some of my requests have huge blocks of binary data, I think this would speed up my application dramatically. However, as far as I can tell, RestSharp does not seem to have any in-built support for BSON.
The use of Json.NET is implemented as a custom serializer for RestSharp, and so at first glance it looks like it would be possible to modify that custom serializer to use BSON. But, the Serialize method of the RestSharp.Serializers.ISerializer interface returns a string - which is (I assume) unsuitable for BSON. So, I assume that it would take some more significant changes to RestSharp to implement this change.
Has anyone figured out a way to do this?
Update: I looked at the RestSharp source, and discovered that the RestRequest.AddBody method that takes my object and serializes it into the request body eventually calls Request.AddParameter (with the serialized object data, and the parameter type RequestBody).
I figured that I might be able to serialize my object to BSON and then call Request.AddParameter directly - and indeed I can. However, when RestSharp then executes the RestRequest, it fails to put the binary content into the request, because there are other embedded assumptions about the request content being UTF-8 encoded.
Thus it looks like this hack would not work - there would need to be some changes made to RestSharp itself, and I'm not the man for the job...
Update 2: I decided to have a go at using the debugger to figure out how much of RestSharp I'd have to change to overcome the body-encoding issue, so I swapped out my NuGet version of RestSharp and included the RestSharp project in my solution. And... it worked.
It turns out that there has been a change to RestSharp in the last few months that isn't yet in the NuGet version.
So, you can now use AddParameter and pass in an already-BSON-encoded object, and RestSharp will send it off to the server without complaint.
Per the updates in my question, it turns out that if you have the latest RestSharp source, then instead of this:
request.AddBody(myObject);
... you can do this instead whenever you have a payload that would benefit from using BSON:
using (var memoryStream = new System.IO.MemoryStream())
{
using (var bsonWriter = new Newtonsoft.Json.Bson.BsonWriter(memoryStream))
{
var serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Serialize(bsonWriter, myObject);
var bytes = memoryStream.ToArray();
request.AddParameter("application/bson", bytes, RestSharp.ParameterType.RequestBody);
}
}
Note that the first parameter to AddParameter is supposedly the parameter name, but in the case of ParameterType.RequestBody it's actually used as the content type. (Yuk).
Note that this relies on a change made to RestSharp on April 11 2013 by ewanmellor/ayoung, and this change is not in the current version on NuGet (104.1). Hence this will only work if you include the current RestSharp source in your project.
Gary's answer to his own question was incredibly useful for serializing restful calls. I wanted to answer how to deserialize restful calls using JSON.NET. I am using RestSharp version 104.4 for Silverlight. My server is using Web API 2.1 with BSON support turned on.
To accept a BSON response, create a BSON Deserializer for RestSharp like so
public class BsonDeserializer : IDeserializer
{
public string RootElement { get; set; }
public string Namespace { get; set; }
public string DateFormat { get; set; }
public T Deserialize<T>(IRestResponse response)
{
using (var memoryStream = new MemoryStream(response.RawBytes))
{
using (var bsonReader = new BsonReader(memoryStream))
{
var serializer = new JsonSerializer();
return serializer.Deserialize<T>(bsonReader);
}
}
}
}
Then, ensure your request accepts "application/bson"
var request = new RestRequest(apiUrl, verb);
request.AddHeader("Accept", "application/bson");
And add a handler for that media type
var client = new RestClient(url);
client.AddHandler("application/bson", new BsonDeserializer());
I am having a RESTful service with the following method:
[WebInvoke]
string GetDataFromStringAsString(string xmlString);
My client call to the method is as below:
var client = new RestClient();
client.BaseUrl = serviceBaseUrl;
var request = new RestRequest(method){RequestFormat = DataFormat.Xml};
request.Resource = resourceUrl;
request.AddParameter("text/xml", requestBody,
ParameterType.RequestBody);
var response = client.Execute(request);
Let us take a string to post as "Hello World".
Now the string that i post to the above method gives me a 400 Bad
request. In order to get it working i had to wrap the above string in
a element as shown below:
<string xmlns="http://schemas.microsoft.com/2003/10/
Serialization/">Hello World</string>
Now when i post the above string i get a success response back from
the server.
Why is that i have to manually wrap the string to make it work. Is
there a way that i can achieve to post a string without doing the
above manually.
The only other way that I am aware of is to use stream as your input parameter. e.g.
[WebInvoke]
string GetDataFromStringAsString(stream xmlString);
The problem with .Net 4 WCF REST is that fundamentally WCF only knows how to pass two types of info, either XML or a stream of bytes. Personally, I would use WCF Web API instead of the standard WCF REST library because you are going run into lots more of these kinds of issues.
I have an ASP application which is client of WCF SERVICE1 , which is client of WCF SERVICE2.
I have added IDispatchMessageInspector and IClientMessageInspector to WCF SERVICE1.
Now I need to pass a custom value from ASP to WCF1 , then to WCF2.
from ASP to WCF1 it is trivial , via Message Headers.
The question is , how to pass a custom value from IDispatchMessageInspector.AfterReceiveRequest(request from ASP received by WCF1) to IClientMessageInspector.BeforeSendRequest(prepare to send request to WCF2) operation of WCF SERVICE 1 ?
Is there is some context which could be used ?
What does your code look like? Assuming that first Dispatch Message Inspector is the one making the request to WCF2, then simply using message properties would suffice.
However, if your dispatch message inspector does something; then the request continues processing and it is the service implementation that actually calls WCF2, then you'll need to jump through a few more hoops. In general, I'd say you'd need the inspector to put some data in the service request message properties that the service implementation would need to pick up and copy to the message to send to WCF2 so that the client inspector can pick them up.
That's ugly, and would kinda make the whole process more brittle.
Can you elaborate a bit more what you're trying to do? What kind of data are you hoping to pass around this way?
In my case, I had to identify and log nested service calls requested by client.
To do that, I stamp each service call by ThreadStatic property and add this property to the header of client call(service1 count as client for service2) than in AfterReceiveRequest method I have checked its existance. If exists,current method was requested by parent service.
public class GenericMessageInspector : IDispatchMessageInspector, IClientMessageInspector
{
[ThreadStatic]
private static string _masterServiceGUID;
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
if (request.Headers.Action == null)
return null;
//Control request header for nested call
string masterRequestId = string.Empty;
var IsMasterExist = request.Headers.FindHeader("MasterServiceGUID", "namespace");
if (IsMasterExist > -1)
{
//requested by internal service
masterRequestId = request.Headers.GetReaderAtHeader(IsMasterExist).ReadInnerXml();
}
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
if (!String.IsNullOrEmpty(_masterServiceGUID))
{
request.Headers.Add(MessageHeader.CreateHeader("MasterServiceGUID", "namespace", _masterServiceGUID));
}
return null;
}
}
}
WCF uses http://tempuri/1/number for Content-ID uri references when handling streamed MTOM requests.
Is there any way how to force WCF to use a different Content-ID references for the xop:Include?
Background of the problem:
I am building a .NET client for MTOM enabled jax ws java web service that handles streamed large data uploads. I have hand crafted the service and data contacts (the WSDL generated contracts were not correct and did not allow streaming).
The problem is that the web service (jax ws) does not receive the request body containing the data.
It receives the data that is transferred in headers.
We have built a java client for the ws - this one works.
I have captured and compared the HTTP traffic when issuing requests from java and wcf, and the only difference is in how Content-ID reference is generated when posting the multipart data:
WCF uses http://tempuri/1/... Content-ID references which yield in encoded value, like href="cid:http%3A%2F%2Ftempuri.org%2F1%2F634019957020047928"
Java client uses "email-style" uris, like href="cid:3f3ec388-8cd9-47aa-a603-fb1bc17935b8#example.jaxws.sun.com"
These yield in the following xop-includes (Data is the only element in the soap body) (XOP includes specification)
//WCF:
<Data>
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="cid:http%3A%2F%2Ftempuri.org%2F1%2F634019957020047928" />
</Data>
//JAVA:
<Data>
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="cid:3f3ec388-8cd9-47aa-a603-fb1bc17935b8#example.jaxws.sun.com"/>
</Data>
later on, in the multipart data, the content is referred to by unencoded Content-ID:
--uuid:7e166bb7-042f-4ba3-b6ef-98fbbc21244b+id=1
Content-ID: <http://tempuri.org/1/634019957020047928>
Content-Transfer-Encoding: binary
Content-Type: application/octet-stream
I guess there may be a bug in the jax web service framework and it is not recognizing WCF-generated+urlencoded Content-ID uri references.
Is there any way how to force WCF to use a different Content-ID references for the xop:Include?
EDIT: I have found the XmlMtomWriter which has the GenerateUriForMimePart method, this is used to generate Content-IDs.
public static string GenerateUriForMimePart(int index)
{
return string.Format(CultureInfo.InvariantCulture,
"http://tempuri.org/{0}/{1}", new object[] { index, DateTime.Now.Ticks });
}
It does not seem that the ID generation is in any way overridable.
A similar issue is described here, the answer provided does not help: http://social.msdn.microsoft.com/Forums/en/wcf/thread/f90affbd-f431-4602-a81d-cc66c049e351
Asnwering to myself after long investigation: Not possible without reimplementing the whole XmlMtomWriter and other related layers and concerns in WCF - almost everything involved in the mtom implementation is internal.
I know it is an old question. But I'm faced the same problem two days ago.
I found a way which works BUT it is a VERY VERY dirty hack (I know that. I thought about not publishing it here but perhaps it would help somebody.) Hopefully you will not blame me for that.
The ContentId is formatted with the use of CultureInfo.InvariantCulture. I didn't find an official way for replacing it with a custom CultureInfo. But with the help of reflection I got it running. The following implementation is only for .Net 4.0.
public class NoTempUriInvariantCultureInfo : CultureInfo, ICustomFormatter
{
private static CultureInfo originalCulture;
private static object originalCultureLock;
private static int enableCounter;
private NoTempUriInvariantCultureInfo(CultureInfo invariantCulture)
: base(invariantCulture.Name)
{
originalCulture = invariantCulture;
}
public static void Enable()
{
if(originalCultureLock == null)
originalCultureLock = new object();
lock (originalCultureLock)
{
if (enableCounter == 0)
{
var mInvCultField = typeof (CultureInfo).GetField("s_InvariantCultureInfo", BindingFlags.NonPublic | BindingFlags.Static);
mInvCultField.SetValue(null, new NoTempUriInvariantCultureInfo(CultureInfo.InvariantCulture));
}
enableCounter++;
}
}
public static void Disable()
{
lock (originalCulture)
{
if (enableCounter == 0)
return;
enableCounter--;
if (enableCounter == 0)
{
var mInvCultField = typeof (CultureInfo).GetField("s_InvariantCultureInfo", BindingFlags.NonPublic | BindingFlags.Static);
mInvCultField.SetValue(null, NoTempUriInvariantCultureInfo.originalCulture);
}
}
}
public override object GetFormat(Type formatType)
{
var result = originalCulture.GetFormat(formatType);
return result ?? this;
}
public string Format(string format, object arg, IFormatProvider formatProvider)
{
if (format == null)
return System.Text.RegularExpressions.Regex.Replace(arg.ToString().Replace("http%3A%2F%2Ftempuri.org%2F1%2F", ""), "http[:][/][/]tempuri[.]org[/][0-9]+[/]*", "");
return String.Format("{0:" + format + "}", arg);
}
}
I enable my own "InvariantCulture" only before a WCF call.
NoTempUriInvariantCultureInfo.Enable();
try
{
// make your call
}
finally
{
NoTempUriInvariantCultureInfo.Disable();
}
CultureInfo.InvariantCulture is a global state object. Enabling my own InvariantCulture affects every other thread.
Again, it is a dirty hack. But it works.
Both of the XOP includes samples that you indicated are correct and acceptable according to the W3C. I refer to them as the URL format and the Email format respectively.
I am not a JAVA developer, but recall a similiar problem when interfacing with a particular JAVA web service. I recall there being a bug in a particular JAVA release and after they (the JAVA developers) upgraded to the next release version, this issue simply went away. I wish I could provide you more details, but at the time, there were enough problems for me to address from my end of the wire and I was just glad to have one less item on the defect log.
//WCF: using URL format
<Data>
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="cid:http%3A%2F%2Ftempuri.org%2F1%2F634019957020047928" />
</Data>
//JAVA: using EMAIL format
<Data>
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="cid:3f3ec388-8cd9-47aa-a603-fb1bc17935b8#example.jaxws.sun.com"/>
</Data>