(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;
}
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´m trying to create a WebServer and it´s working with HTML, Images...
But I want to implement PHP and I have a working implementation of PHP-CGI but it doesn't work with stuff like 'file_get_contents' and cookies.
And I was wondering if there is a way to fix that?
private string ProcessPHP(string phpPath, CookieCollection cookies)
{
Process CGI = new Process();
CGI.StartInfo.FileName = "php\\php.exe";
CGI.StartInfo.CreateNoWindow = true;
CGI.StartInfo.Arguments = "-q \"" + phpPath + "\"";
CGI.StartInfo.RedirectStandardOutput = true;
CGI.StartInfo.UseShellExecute = false;
CGI.Start();
string OutputText = CGI.StandardOutput.ReadToEnd();
CGI.WaitForExit();
CGI.Close();
return OutputText;
}
And I know that I´m not doing anything with the CookieCollection but I want to know how to use it.
Thank you!
This is an old question, but currently the windows version of php ships with a separate php-cgi.exe. That's the executable you'll have to run.
With CGI, script information and request headers are sent via environment variables, ie:
CGI.StartInfo.info.EnvironmentVariables["SCRIPT_FILENAME"] = #"c:/wwwroot/index.php";
For a more elaborate example see https://en.wikipedia.org/wiki/Common_Gateway_Interface
HTTP headers such as cookies are also sent via environment variables. They are prefixed with 'HTTP_' and dashes are converted to underscores:
CGI.StartInfo.info.EnvironmentVariables["HTTP_COOKIE"] = ...
If your request body contains data, you have to set the CONTENT_LENGTH environment variable and write the data to the CGI.StandardInput after starting the process.
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.
I am sending email body to the console application as parameter, the thing is I only see first 4 characters <div on the console application what happens to the other part? Can I send html email text as parameter to console application? Also is there any way to return a string[] array from console app?
My so far code below:
TestingConsoleApp to check the send n receive:
static void Main(string[] args)
{
string emailBody = System.IO.File.ReadAllText(#"C:\Users\ehsankayani\Desktop\email1Html.txt");
CallProcess(emailBody);
}
static void CallProcess(string body)
{
string path = #"F:\Scrappers\emailParser_app\emailParser_app\bin\Debug\emailParser_app.exe";
Process.Start(path, body);
}
Main console app:
static void Main(string[] args)
{
Console.WriteLine("EMAIL BODY = ");
string[] dataToReturn = new string[8];
//string emailBody = System.IO.File.ReadAllText(#"C:\Users\ehsankayani\Desktop\email1Html.txt");
string emailBody = args[0];
Console.WriteLine(emailBody);
Console.WriteLine(emailBody.Length);
Console.ReadLine();
var doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(emailBody);
}
Any suggestions?
You are passing body as the parameter set in CallProcess. So if body were eg
xxxx yyyy ....
then args[0] would be just xxxx. You'll need to put "" around the text and escape in "'s in the text too.
A far better solution though would be to set up your Process to redirect stdin and to write the body to the process' stdin. This will avoid issues with whitespace and quotes. Take a look at http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.redirectstandardinput.aspx for details on doing this.
A simply HttpUtility.EncodeHtml(Emailbody) while sending and HttpUtility.DecodeHtml(emailBody) on receiving does the job.
Since you ask for suggestions: For complex and/or large data, I'd rather pass only the information where to find the data to the process being called. Same can be done for returned data
This has several positive effects, the more complex and the larger these data are: You don't have to make sure the data are matching the command line restrictions, you don't have to allocate lots of memory to encode/decode the data in both processes, and you don't need to implement the logic to do that. Instead, write the data into a file, a shared memory region (MMF) or the like and pass that address.
Nearly the only downside is that you have to think about who's responsible for cleaning up.
Another possible approach could involve interprocess communication, but I think that's a bit overkill here.
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]);
}