I'm working on a problem with imap sort extention:
My command is the following:
var query = "icône";
//byte[] bytes = Encoding.Default.GetBytes(query);
//query = Encoding.UTF8.GetString(bytes);
var command = "SORT (REVERSE ARRIVAL) UTF-8 " + "{" + query.Length + "}";
var imapAnswerString = client.Command(command);
imapAnswerString = client.Command(query);
I get the following error:
BAD Error in IMAP command SORT: 8bit data in atom
I found this:
C# Imap search command with special characters like á,é
But I don't see how to prepare my code to send this request sucessfully.
If you want to stick with MailSystem.NET, the answer that arnt gave is correct.
However, as I point out here (and below for convenience), MailSystem.NET has a lot of architectural design problems that make it unusable.
If you use an alternative open source library, like MailKit, you'd accomplish this search query far more easily:
var query = SearchQuery.BodyContains ("icône");
var orderBy = new OrderBy[] { OrderBy.ReverseArrival };
var results = folder.Search (query, orderBy);
Hope that helps.
Architectural problems in MailSystem.NET include:
MailSystem.NET does not properly handle literal tokens - either sending them (for anything other than APPEND) or for receiving them (for anything other than the actual message data in a FETCH request). What none of the authors seem to have noticed is that a server may choose to use literals for any string response.
What does this mean?
It means that the server may choose to respond to a LIST command using a literal for the mailbox name.
It means that any field in a BODYSTRUCTURE may be a literal and does not have to be a quoted-string like they all assume.
(and more...)
MailSystem.NET, for example, also does not properly encode or quote mailbox names:
Example from MailSystem.NET:
public string RenameMailbox(string oldMailboxName, string newMailboxName)
{
string response = this.Command("rename \"" + oldMailboxName + "\" \"" + newMailboxName + "\"");
return response;
}
This deserves a Jean-Luc Picard and Will Riker face-palm. This code just blindly puts double-quotes around the mailbox name. This is wrong for at least 2 reasons:
What if the mailbox name has any double quotes or backslashes? It needs to escape them with \'s.
What if the mailboxName has non-ASCII characters or an &? It needs to encode the name using a modified version of the UTF-7 character encoding.
Most (all?) of the .NET IMAP clients I could find read the entire response from the server into 1 big string and then try and parse the response with some combination of regex, IndexOf(), and Substring(). What makes things worse is that most of them were also written by developers that don't know the difference between unicode character counts (i.e. string.Length) and octets (i.e. byte counts), so when they try to parse a response to a FETCH request for a message, they do this after parsing the "{}" value in the first line of the response:
int startIndex = response.IndexOf ("}") + 3;
int endIndex = startIndex + octets;
string msg = response.Substring (startIndex, endIndex - startIndex);
The MailSystem.NET developers obviously got bug reports about this not working for international mails, so their "fix" was to do this:
public string Body(int messageOrdinal)
{
this.ParentMailbox.SourceClient.SelectMailbox(this.ParentMailbox.Name);
string response = this.ParentMailbox.SourceClient.Command("fetch "+messageOrdinal.ToString()+" body", getFetchOptions());
return response.Substring(response.IndexOf("}")+3,response.LastIndexOf(" UID")-response.IndexOf("}")-7);
}
Essentially, they assume that the UID key/value pair will come after the message and use that as a hack-around for their incompetence. Unfortunately, adding more incompetence to existing incompetence only multiplies the incompetence, it doesn't actually fix it.
The IMAP specification specifically states that the order of the results can vary and that they may not even be in the same untagged response.
Not only that, but their FETCH request doesn't even request the UID value from the server, so it's up to the server whether to return it or not!
TL;DR
How to Evaluate an IMAP Client Library
The first thing you should do when evaluating an IMAP client library implementation is to see how they parse responses. If they don't use an actual tokenizer, you can tell right off the bat that the library was written by people who have no clue what they are doing. That is the most sure-fire warning sign to STAY AWAY.
Does the library handle untagged ("*") responses in a central place (such as their command pipeline)? Or does it do something retarded like try and parse it in every single method that sends a command (e.g. ImapClient.SelectFolder(), ImapClient.FetchMessage(), etc)? If the library doesn't handle it in a central location that can properly deal with these untagged responses and update state (and notify you of important things like EXPUNGE's), STAY AWAY.
If the library reads the entire response (or even just the "message") into a System.String, STAY AWAY.
You're almost there. Your final command should be something like
x sort (reverse arrival) utf-8 subject {6+}
icône
ie. you're just missing a search term to describe where the IMAP server should search for icône and sort the results. There are many other search keys, not just subject. See RFC3501 page 49 and following pages.
Edit: The + is needed after the 6 in order to send that as a single command (but requires that the server support the LITERAL+ extension). If the server doesn't support LITERAL+, then you will need to break up your command into multiple segments, like so:
C: a001 SORT (REVERSE ARRIVAL) UTF-8 SUBJECT {6}
S: + ok, whenever you are ready...
C: icône
S: ... <response goes here>
Thanks all for your answer.
Basically the way MailSystem.net sends requests (Command method) is the crux of this problem, and some others actually.
The command method should be corrected as follows:
First, when sending the request to imap, the following code works better than the original one:
//Convert stuff to have to right encoding in a char array
var myCommand = stamp + ((stamp.Length > 0) ? " " : "") + command + "\r\n";
var bytesUtf8 = Encoding.UTF8.GetBytes(myCommand);
var commandCharArray = Encoding.UTF8.GetString(bytesUtf8).ToCharArray();
#if !PocketPC
if (this._sslStream != null)
{
this._sslStream.Write(System.Text.Encoding.UTF8.GetBytes(commandCharArray));
}
else
{
base.GetStream().Write(System.Text.Encoding.UTF8.GetBytes(commandCharArray), 0, commandCharArray.Length);
}
#endif
#if PocketPC
base.GetStream().Write(System.Text.Encoding.UTF8.GetBytes(commandCharArray), 0, commandCharArray.Length);
#endif
Then, in the same method, to avoid some deadlock or wrong exceptions, improve the validity tests as follows:
if (temp.StartsWith(stamp) || temp.ToLower().StartsWith("* " + command.Split(' ')[0].ToLower()) || (temp.StartsWith("+ ") && options.IsPlusCmdAllowed) || temp.Contains("BAD Error in IMAP command"))
{
lastline = temp;
break;
}
Finally, update the return if as follows:
if (lastline.StartsWith(stamp + " OK") || temp.ToLower().StartsWith("* " + command.Split(' ')[0].ToLower()) && !options.IsSecondCallCommand || temp.ToLower().StartsWith("* ") && options.IsSecondCallCommand && !temp.Contains("BAD") || temp.StartsWith("+ "))
return bufferString;
With this change, all commands work fine, also double call commands. There are less side effects than with the original code.
This resolved most of my problems.
Related
After trudging around the net (and GitHub source and issues), I did find this SO post that describes essentially the same problem, and a "solution" for it (SHIFT+ENTER, which equates to "\r\n", in the Resx editor). This seems hackish and far less than ideal (I see it leading to inconsistent line terminations.)
However, while digging through the source to try and reproduce it or see if there was a known issue, I cloned the ms repo here.
git clone -b master https://github.com/aspnet/Localization.git
Master is important (dev wouldn't compile.) It does the upgrade for the project files (I am using VS2017, v15.2) without issue. Change "samples\LocalizationSample" to be the startup project.
I add a using System.Text to the top of Startup.cs and then by adding the following at original line 66...
const string HELLO = "Hello";
var name = "John Doe";
var sb = new StringBuilder($"{name}\n");
// Similiar to what I am currently using that is not working.
// However, tmplt gets the expected value here in this cloned repo!
// Yet, after the AppendFormat call below, it is back to escaped "\n" ("\\n")
var tmplt = SR[HELLO]; // A
// In my code that started this rabbit hole, tmplt gets the
// following via my injected _localizer/resx.
//var tmplt = "{0}:\\n Score: {1}\\n Pct: {2}\\n\\n"; // B
// Comment out above tmplt lines, and uncomment this and it works as expected
// This is the string that I have in my resx
//var tmplt = "{0}:\n Score: {1}\n Pct: {2}\n\n"; // C
string subj = "Subject";
int score = 100;
int? pct = 100;
sb.AppendFormat(
tmplt, // A = "\n"!, B = "\\n" (expected), C = "\n" (known good)
subj,
score,
pct.Value
);
// A = "\\n" :-(
// B = "\\n"
// C = "\n"
var msg = sb.ToString();
var just_a_place_to_set_a_breakpoint = true;
I then duplicate the Startup.es-ES.resx file as Startup.en-US.resx, then change the value for the single key in my new resx to match my string shown in "C" above (// C)
I can now set a breakpoint at just_a_place_to_set_a_breakpoint and fire up the project.
It is able to correctly get the value out without the extra escape character before the newline from this project's localization data! I haven't figured out why that much works in this cloned/modified repo, but not in my original private repo.
However, once it gets to the ToString call, it is back to the unwanted escaped newline ("\n") in both scenarios A and B! :-(
How is it that the tmplt variable contains the correct
representation in this hacked cloned repo when it pulls the
localization string out, but not in mine?
How is it that, regardless, by the time the StringBuilder.ToString is
called, unless I supply a hardcoded string in the source directly, the
"\n" characters ended up escaped ("\n") when it may not have been
present in the string to be formatted?
Any help greatly appreciated, my eyeballs are about to fall out (and I fear this is something easy/documented and Google just failed to turn it up.)
UPDATE: I found the branch that I presume should match the version of the Microsoft.Extensions.Localization my original project is using, v1.1.2... but it fails to build.
I am relatively new to programming, so please excuse me if the question is stupid. I am now working on a project which involves Kinect. I am using C# to extract the live joint information (such as position and orientation) and then sending the data into Processing using OSC message -Udp protocal. I sent OSC message bundle from C# the problem is I don't know how to dispatch the message to what I want in Processing. Or possibly, I sent the data in a wrong format in C#. I would be very appreciate if someone can tell me what could possible gone wrong in the code and caused the errors.
I send the joint position from C# by using these code:
if (joint0.JointType == JointType.ElbowRight)
{
// distance in meter
String temp = "ElbowRight " + joint0.Position.X * 1000 + " " + joint0.Position.Y * 1000 + " " + joint0.Position.Z * 1000;
Console.WriteLine(temp);
OscElement message = new OscElement("/joint/" + joint0.JointType.ToString(), joint0.Position.X * 1000, joint0.Position.Y * 1000, joint0.Position.Z, joint0.TrackingState.ToString());
bundle.AddElement(message);
}
OscSender.Send(bundle); // send message bundle
The part,"/joint/", is the address pattern of the message. The following data are the arguments of the message. According to http://opensoundcontrol.org/spec-1_0 a OSC type tag string should be added following to the address pattern, which is an OSC-string beginning with the character ',' (comma) followed by a sequence of characters corresponding exactly to the sequence of OSC Arguments in the given message. However when I tried this, a format exception was caused and the error was reported as: Invalid character (\44). What I did was simply added ",s" to the OSC message:
OscElement message = new OscElement("/joint/" + ",s" + joint0.JointType.ToString(), joint0.Position.X * 1000, joint0.Position.Y * 1000, joint0.Position.Z, joint0.TrackingState.ToString());
How do I suppose to add the type tag? Can this be the reason that caused the following errors?
In my Processing code, I tried to get the joint position value by using these code:
if(theOscMessage.checkAddrPattern("/joint")==true) {
String firstValue = theOscMessage.get(0).stringValue();
float xosc = theOscMessage.get(1).floatValue(); // get the second osc argument
float yosc = theOscMessage.get(2).floatValue(); // get the second osc argument
float zosc = theOscMessage.get(3).floatValue(); // get the second osc argument
String thirdValue = theOscMessage.get(4).stringValue(); // get the third osc argument
println("### values: "+xosc+", "+xosc+", "+zosc);
return;
}
However I got this error:
[2013/6/16 20:20:53] ERROR # UdpServer.run() ArrayIndexOutOfBoundsException: java.lang.ArrayIndexOutOfBoundsException
Than I tied receiving the messaging using a example given in Processing, which displays the address pattern and type tag of the massage:
println("addrpattern\t"+theOscMessage.addrPattern());
println("typetag\t"+theOscMessage.typetag());
It printed out this:
addrpattern pundle
typetag u???N?N?$xlt???
I dont understand what's wrong with the code. Souldn't the address pattern be "joint"? or at least "bundle"? What is the pundle...
P.s. I am using Visual C# 2010 Express and Processing 2.0b9 64bit on a Win7 os computer.
Thank you so much for the help!
Update:
Although I still can't figure out how to solve this problem, I found a way to receive messages in Processing. Instead of using OSC bundle, I am sending Osc messages with different address pattern. then use message plug (e.g. oscP5.plug(this,”leftFoot”,”/joint/AnkleLeft”);) in the draw method. Then create a method called leftFoot
public void leftFoot(float fx, float fy, float fz, String state) {
println("Received: "+fx+", "+fy+", "+fz+", "+state);
}
Then you can see the data being print out. p.s. in C# the OSC message was send using:
OscElement message = new OscElement("/joint" + "/" + joint0.JointType.ToString(), joint0.Position.X * 1000, joint0.Position.Y * 1000, joint0.Position.Z, joint0.TrackingState.ToString());
OscSender.Send(message);
Hmmm... not sure exactly, but you could just use OSCeleton. It comes with a Processing example - I've used it before, and it works fine.
(the example may also help you understand how to use the OSC address patterns correctly... )
https://github.com/Sensebloom/OSCeleton
https://github.com/Sensebloom/OSCeleton-examples
OSC requires padding to next 32-bit boundary, meaning that you need to add zeros to pad the message, until its length is an even multiple of four bytes.
You need to do the same for the typetag. Pad it with zeroes even if you only send one typetag, so ,s00 instead of just ,s
Hoping someone can shed some light on this issue we are having because I'm at a loss here.
First, a little background:
I rewrote the URL rewriting for our application and implemented it a couple of weeks ago. I did this using Application_BeginRequest() in the global.asax file and everything was fine with our application except for a small oversight I had made. When I'm rewriting the URLs I'm simply checking for the existence of certain keywords in the path that the user requests and then rewriting the path accordingly. Pretty straight forward stuff, not inventing the wheel here. Dry code, really. However, the text I'm checking for is all lowercase while the path may come in with different cases.
For instance:
string sPath = Request.Url.ToString();
sPath = sPath.Replace(Request.Url.Scheme + "://", "")
.Replace(Request.Url.Host, "");
if (sPath.TrimStart('/').TrimEnd('/').Split('/')[0].Contains("reports") && sPath.TrimStart('/').TrimEnd('/').Split('/').Length > 2) {
string[] aVariables = sPath.TrimStart('/').TrimEnd('/').Split('/');
Context.RewritePath("/app/reports/report-logon.aspx?iLanguageID=" + aVariables[1] + "&sEventCode=" + aVariables[2]);
}
...if someone enters the pages as /Reports/, the rule will not match and they will receive a 404 error as a result.
Simple to fix, though, I thought. One only needs to force the requested path string to lowercase so that anything I attempt to match against it will be looking at a lowercase version of the requested path, and match successfully in cases such as the above. So I adjusted the code to read:
string sPath = Request.Url.ToString();
sPath = sPath.Replace(Request.Url.Scheme + "://", "")
.Replace(Request.Url.Host, "");
sPath = sPath.ToLower(); // <--- New line
if (sPath.TrimStart('/').TrimEnd('/').Split('/')[0].Contains("reports") && sPath.TrimStart('/').TrimEnd('/').Split('/').Length > 2) {
string[] aVariables = sPath.TrimStart('/').TrimEnd('/').Split('/');
Context.RewritePath("/app/reports/report-logon.aspx?iLanguageID=" + aVariables[1] + "&sEventCode=" + aVariables[2]);
}
With this fix, when I request any URL that matches against the URL rewriting, however, the CPU on the server spikes to 100% and my entire application crashes. I take out .ToLower(), kill the app pool, and the application is perfectly fine again.
Am I missing something here!?!? What gives? Why does such a simple method cause my application to explode? .ToLower() works everywhere else in our application, and although I'm not using it extensively, I am using it quite successfully in other places around the application.
Not sure exactly why ToLower would cause this (only thing I can think of is that it is modifying request.url, which sends asp.net into a frenzy), but there is an easy fix: use an ignorecase comparison rather than converting everything tolower.
Change:
sPath.TrimStart('/').TrimEnd('/').Split('/')[0].Contains("reports")
to:
sPath.TrimStart('/').TrimEnd('/').Split('/')[0].IndexOf("reports", StringComparison.InvariantCultureIgnoreCase) != -1
and remove your ToLower logic.
Though I can't say why .toLower() is bringing your server down
Why dont you try it with indexOf
if (sPath.TrimStart('/').TrimEnd('/').Split('/')[0].IndexOf("reports",StringComparison.InvariantCultureIgnoreCase)>=0 && sPath.TrimStart('/').TrimEnd('/').Split('/').Length > 2)
{
string[] aVariables = sPath.TrimStart('/').TrimEnd('/').Split('/');
Context.RewritePath("/app/reports/report-logon.aspx?iLanguageID=" + aVariables[1] + "&sEventCode=" + aVariables[2]);
}
I'm using Jon Skeet's (excellent) port of Google's Protocol Buffers to C#/.Net.
For practice, I have written a dummy Instant Messenger app that sends some messages down a socket. I have a message definition as follows:-
message InstantMessage {<br/>
required string Message = 1;<br/>
required int64 TimeStampTicks = 2; <br/>
}
When the sender serialises the message, it sends it really elegantly:-
...
InstantMessage.Builder imBuild = new InstantMessage.Builder();
imBuild.Message = txtEnterText.Text;
imBuild.TimeStampTicks = DateTime.Now.Ticks;
InstantMessage im = imBuild.BuildPartial();
im.WriteTo(networkStream);
...
This works great. But at the other end, I'm having trouble getting the ParseFrom to work.
I want to use:-
InstantMessage im = InstantMessage.ParseFrom(networkStream);
But instead I have had to read it to bytes and then parse it from here. This is obviously not ideal for a number of reasons. Current code is:-
while (true)
{
Byte[] byteArray = new Byte[10000000];
int intMsgLength;
int runningMsgLength = 0;
DateTime start = DateTime.Now;
while (true)
{
runningMsgLength += networkStream.Read(byteArray, runningMsgLength, 10000000 - runningMsgLength);
if (!networkStream.DataAvailable)
break;
}
InstantMessage im = InstantMessage.ParseFrom(byteArray.Take(runningMsgLength).ToArray());
When I try to use ParseFrom, control does not return to the calling method even when I know a valid GB message is on the wire.
Any advice would be gratefully received,
PW
Sorry for taking a while to answer this. As Marc says, protocol buffers don't have a terminator, and they aren't length prefixed unless they're nested. However, you can put on the length prefix yourself. If you look at MessageStreamIterator and MessageStreamWriter, you'll see how I do this - basically I pretend that I'm in the middle of a message, writing a nested message as field 1. Unfortunately when reading the message, I have to use internal details (BuildImpl).
There's now another API to do this: IMessage.WriteDelimitedTo and IBuilder.MergeDelimitedFrom. This is probably what you want at the moment, but I seem to remember there's a slight issue with it in terms of detecting the end of the stream (i.e. when there isn't another message to read). I can't remember whether there's a fix for it at the moment - I have a feeling it's changed in the Java version and I may not have ported the change yet. Anyway, that's definitely the area to look at.
Protobuf has no terminator - so either close the stream, or use your own length prefix etc. Protobuf-net exposes this easily via SerializeWithLenghtPrefix / DeserializeWithLengthPrefix.
Simply: without this, it can't know where each message ends, so keeps trying to read to the end of the stream.
(warning - unholy mixture of xml and gratuitous character encoding below.)
Short version:
Why can't I get my service reference call (c#, .net 3.5, automagic service reference code added to VS2008) to properly encode a parameter that's supposed to look like this one the wire: (look for the " bits...those are my bane.)
(other extra soap-y bits removed for clarity)
<SOAP-ENV:Body><SOAPSDK4:SetCondition xmlns:SOAPSDK4="http://tempuri.org/message/">
<sharedSecret>buggerall</sharedSecret>
<xmlData><SEARCHINFO_LIST><SEARCH_INFO action="add" status=&
quot;3" name="TestProfile2" mask="0" campaign_id="33"
campaign_protected="N" condition_protected="N"><CONDITIONS/&
gt;<EXPRESSIONS/></SEARCH_INFO></SEARCHINFO_LIST></xmlData>
</SOAPSDK4:SetCondition></SOAP-ENV:Body>
I set the parameter, make the call...and the service returns a nice message saying "NO SOU-- SOAP FOR YOU!"
I tried several other formats in my passed-to-the-webservice parameter string:
action=\"add\"
which gave me this on the wire (via fiddler): action="add"
action="add"
which gave me this on the wire: action="add"
and various combinations (action=""add"" ?! ) with html.encode, url.encode which pretty much either completely bombed, or showed as double quotes on the wire.
Oh, and I even tried <![CDATA["]] surrounding. That didn't work either.
Is there any way to force a double quote encoding in the innerHtml bit of the soap message?
*(because that's how the service wants them. don't ask questions. these aren't the droids you're looking for)
*
*
*
Long, tortuous version:
I'm writing an app to automate some procedures that are currently handled by
a (winform) administrative GUI app. (Actually, it's an mmc snap in.
Anyway.)
To accomplish it's tasks, the winform app communicates with it's server via
standard web service calls.
I'm using VS2008's nifty "web service reference" auto generation thingie
(that's the technical description), and I've successfully authenticated to
the web service. To make sure I was doing things correctly, I captured the
calls from the GUI app, and then compared them to what I was sending out on
the wire. All was good. Then I ran into the evils of the ampersand. (more
properly, how to get things to encode properly)
For one of the calls, the web service expects to see something like this:
(I captured the app sending this via fiddler)
<?xml version="1.0" encoding="UTF-8" standalone="no"?><SOAP-ENV:Envelope
xmlns:SOAPSDK1="http://www.w3.org/2001/XMLSchema" xmlns:SOAPSDK2="
http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAPSDK3="
http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="
http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><SOAPSDK4:SetCondition
xmlns:SOAPSDK4="http://tempuri.org/message/"><sharedSecret>0500001007C3525F3-F315-460D-
AF5C-D84767130126094</sharedSecret><xmlData><SEARCHINFO_LIST><SEARCH_INFO
action="add" status="3" name="TestProfile2" mask=&
quot;0" campaign_id="33"campaign_protected="N"
condition_protected="N"><CONDITIONS/><EXPRESSIONS/>&
lt;/SEARCH_INFO></SEARCHINFO_LIST></xmlData></SOAPSDK4:SetCondition></SOAP-
ENV:Body></SOAP-ENV:Envelope>
Stripping out all the extra SOAP-y stuff to show the relevant bit - this is the <xmlData> section that's passed across. Notice the " surrounding the parameters:
<SEARCHINFO_LIST><SEARCH_INFO action="add"
status="3" name="TestProfile2" mask="0"
campaign_id="33" campaign_protected="N"
condition_protected="N"><CONDITIONS/><EXPRESSIONS/>&
lt;/SEARCH_INFO></SEARCHINFO_LIST>
In my code, I have a string built up like so:
var serviceParams = "<SEARCHINFO-LIST><SEARCH_INFO action=\"add\"
status=\"3\" name=\"TestProfileFromExternApp\" mask=\"0\" campaign_id=\"33\"
campaign_protected=\"N\"
condition_protected=\"N\"><CONDITIONS/><EXPRESSIONS/></SEARCH_INFO></SEARCHINFO_LIST>";
When my app sends it out over the wire, fiddler captures this: (again,
stripping out all the SOAP stuff)
<SEARCHINFO-LIST><SEARCH_INFO action="add" status="3"
name="TestProfileFromExternApp" mask="0" campaign_id="33"
campaign_protected="N" condition_protected="N"><CONDITIONS/>
<EXPRESSIONS/></SEARCH_INFO></SEARCHINFO_LIST>
And the receiving service sends back an error that it doesn't like it. It wants the " d##nit.
The angle brackets get properly encoded, but the quotes are valid in the
HTTP string, and do not get encoded.
"Ah-ha!" says I, "I'll just pre-manually encode things!". I attempted to
do something like this:
var serviceParams = "<SEARCHINFO-LIST><SEARCH_INFO action="add"
status="3" name="TestProfileFromExternApp"
mask="0" campaign_id="33"
campaign_protected="N"
condition_protected="N"><CONDITIONS/><EXPRESSIONS/></SEARCH_INFO></SEARCHINFO_LIST>";
Which was sent out as (again, via fiddler) and all my ampersands (in the
") get converted to " like so:
<SEARCHINFO-LIST><SEARCH_INFO action="add"
status="3"
name="TestProfileFromExternApp" mask="0"
campaign_id="33" campaign_protected="N"
condition_protected="N"><CONDITIONS/><EXPRESSIONS/></SEARCH_INFO></SEARCHINFO_LIST>
And, as you can guess, the receiving web service came back with "BZZT!
Thank you for playing!".
I've tried all sorts of escape and encode sequences with similar results.
Effectively, after all my manipulation it goes through something like
HttpUtility.HtmlEncode right before going out on the wire, and any
ampersands in the string get converted to &. And quotes (single or
double) are ignored in the conversion. And the receiving web service
wants those quotes represented as " doggone it, or it's going to
take it's ball and go home.
My last, desperate hope was to catch the message right (I thought) before it went onto the wire using IClientMessageInspector to implement message inspection in the BeforeSendRequest event...and manually set those things before it went to the wire.
I capture the message fine. I can even manually put in ".
But when it gets sent, both wireshark and fiddler assure me it's going out nicely formatted...with the quotes I'm so desperately trying to get rid of.
<xmlData xsi:type="xsd:string" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SEARCHINFO-LIST><SEARCH_INFO action="add" status="3"
name="TestProfileFromExternApp" mask="0" campaign_id="33" campaign_protected="N"
condition_protected="N"><CONDITIONS/><EXPRESSIONS/>
</SEARCH_INFO></SEARCHINFO_LIST></xmlData>
I'm at my wits end. I'll accept any suggestions up to and including sacrificing [small cuddly thing] on the alter of [vile deity] or selling same my [soul/heart/son's bionacle collection]. Trust me, it'd be the lesser evil.
Per request below, here's the generated message stub:
(I think this is what you were asking for...)
public int SetCondition(string sharedSecret, string xmlData, out string resultValue)
{
tzGui.tzCampaign.SetConditionRequest inValue = new tzGui.tzCampaign.SetConditionRequest();
inValue.sharedSecret = sharedSecret;
inValue.xmlData = xmlData;
tzGui.tzCampaign.SetConditionResponse retVal = ((tzGui.tzCampaign.CampaignSoapPort)(this)).SetCondition(inValue);
resultValue = retVal.resultValue;
return retVal.Result;
}
And here's how it's getting called:
void SetConditionTask()
{
//ok, now we *try* and create a new profile
var tzCampaignCxn = new tzCampaign.CampaignSoapPortClient("CampaignSoapBinding");
//no worky
//string xmlData = "<SEARCHINFO-LIST><SEARCH_INFO action=\"add\" status=\"3\" name=\"TestProfileFromExternApp\" mask=\"0\" campaign_id=\"33\" campaign_protected=\"N\" condition_protected=\"N\"><CONDITIONS/><EXPRESSIONS/></SEARCH_INFO></SEARCHINFO_LIST>";
//this one doesn't work
//string xmlData = "<SEARCHINFO-LIST><SEARCH_INFO action=<![CDATA[ " ]]>add<![CDATA[ " ]]> status=<![CDATA[ " ]]>3<![CDATA[ " ]]> name=<![CDATA[ " ]]>TestProfileFromExternApp<![CDATA[ " ]]> mask=<![CDATA[ " ]]>0<![CDATA[ " ]]> campaign_id=<![CDATA[ " ]]>33<![CDATA[ " ]]> campaign_protected=<![CDATA[ " ]]>N<![CDATA[ " ]]> condition_protected=<![CDATA[ " ]]>N<![CDATA[ " ]]>><CONDITIONS/><EXPRESSIONS/></SEARCH_INFO></SEARCHINFO_LIST>";
//this one doesn't either
string xmlData = "<SEARCHINFO-LIST><SEARCH_INFO action="add" status="3" name="TestProfileFromExternApp" mask="0" campaign_id="33" campaign_protected="N" condition_protected="N"><CONDITIONS/><EXPRESSIONS/></SEARCH_INFO></SEARCHINFO_LIST>";
string createProfileResultVal = string.Empty;
tzCampaignCxn.SetCondition(SharedSecret, xmlData, out createProfileResultVal);
txtResults.AppendText(Environment.NewLine + Environment.NewLine + createProfileResultVal);
}
If I understand your long post, you seem to be building parts of your XML by using strings. Don't do that. Always use one of the XML APIs to create XML. It knows the quoting rules.
You need to get that string into the SOAP message as a CDATA section, so that you can format it exactly as you want, and so that WCF doesn't touch it.
The problem you have at the moment is that the service is expecting an encoding that somewhere between xml and html - which is therefore not nicely handled by format that the Soap formatter is using - so you are always going to have to escape the quotes yourself.
I have a hunch, though, that if you surround it in the CDATA tags <![CDATA[*yourstring*]]> in your string parameter, the markup for that will be escaped over the wire and reach the other end as <![[*yourstring-XML-element-encoded*]]>
If that's the case, what you could do is to alter the code that was generated automatically by the VS service reference generator so that the parameter type of the method call on the proxy accepts a type that is basically the same as a string, but serializes itself as a CDATA section. Such a type is provided based on code written by Marc Gravell to answer another question here. The problem with doing this will be that if anyone uses the 'Update Reference' command in VS on that service reference, any changes you make will be lost.
So, instead, generate the reference with the svcutil command line utility (contrary to popular belief, VS doesn't use this tool - which is a shame because it is more flexible), import the config and code it generates into your project manually and get rid of the 'service reference'. That way, the code is yours to hack however you want, and you can easily see it in the project tree in VS.
I never did fully figure out how to get this to work. However, I did find a solution. An ugly, hackish solution that I'm not proud of. But it worked.
In the interest of posterity, here's what I finally did. For each of the (10ish) calls I needed to make, I simply captured the SOAP call via fiddler, and then wrote a quick search-n-replace routine
public string Newrule(string ruleName, DecisionSet decisionSet)
{
var soapString = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><SOAP-ENV:Envelope " +
"xmlns:SOAPSDK1=\"http://www.w3.org/2001/XMLSchema\" xmlns:SOAPSDK2=\"http://www.w3.org/2001/XMLSchema-instance\" " +
"xmlns:SOAPSDK3=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
"<SOAP-ENV:Body><SOAPSDK4:SetCondition xmlns:SOAPSDK4=\"http://tempuri.org/message/\"><sharedSecret></sharedSecret>" +
"<xmlData><SEARCHINFO_LIST><SEARCH_INFO action="add" status="3" " +
"name="" + ruleName + "" mask="0" DecisionSet_id="" + decisionSet.Id +
"" DecisionSet_protected="N" " +
"condition_protected="N"><CONDITIONS/><EXPRESSIONS/></SEARCH_INFO></SEARCHINFO_LIST>" +
"</xmlData></SOAPSDK4:SetCondition></SOAP-ENV:Body></SOAP-ENV:Envelope>";
var headerUrl = "http://tempuri.org/action/DecisionSet.SetCondition";
var serviceUrl = "/webservice/DecisionSet.WSDL";
var result = sender.MakeRequest(soapString, serviceUrl, headerUrl,null);
var idSearch = #"SEARCH_INFO id="(\d+)"";
var ruleId = Regex.Match(result, idSearch).Groups[1].Value;
return ruleId;
}
This called a simple routine to make an http call with the appropriate headers.
Inelegant, but it worked.
public string MakeRequest(string requestString, string serviceUrl, string headerUrl, string useragent)
{
string query = requestString.Replace(#"<sharedSecret></sharedSecret>", "<sharedSecret>"+secret+"</sharedSecret>");
query = query.Replace(#"<SessionID></SessionID>", "<SessionID>" + secret + "</SessionID>");
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(server + serviceUrl);
//if (proxy != null) req.Proxy = new WebProxy(proxy, true);
req.Headers.Add("SOAPAction", headerUrl);
if (useragent == null)
req.UserAgent = "SOAP Toolkit 3.0";
else
{
req.UserAgent = useragent;
}
req.ContentType = "text/xml;charset=\"utf-8\"";
req.Accept = "text/xml";
req.Method = "POST";
Stream stm = req.GetRequestStream();
StreamWriter sw = new StreamWriter(stm);
sw.Write(query);
sw.Flush();
stm.Close();
WebResponse resp = req.GetResponse();
stm = resp.GetResponseStream();
StreamReader r = new StreamReader(stm);
string response = (r.ReadToEnd());
return response;
}