HttpWebRequest maintenance and http web errors causing it to return "HRESULT E_FAIL" and "server not found" - rest

I am iterating through a large list of objects (1503) and calling a save method on a ServiceProxy I have written. The service proxy uses the new networking stack in Silverlight 4 to call BeginGetRequestStream to start the process of asynchronously sending my objects to an azure REST service I have written for saving off the objects. The Http method I am using is POST. I know HttpWebClient is smart enough to reuse the Http connection so I am not concurrently opening 1503 connections to the server. Saving works fine and all 1503 objects are saved very quickly. However, when I try to save the same objects again, I expect to recieve an HttpStatus code of forbidden because the objects already exist and that is the code I set my azure web service to return. On small groups of objects, it works as expected. However, when I try saving the entire list of 1503 objects, I receive only 455 correct responses and 1048 errors such as "server not found" and
System.Exception ---> System.Exception:Error HRESULT E_FAIL has been returned from a call to a COM component.
at
System.Net.Browser.ClientHttpWebRequest.InternalEndGetResponse(IAsyncResult asyncResult)...
I wonder if there is some sort of book keeping or maintenance I am supposed to be performing on my HttpWebClient instances that I am neglecting and that is what is causing the http errors to throw exceptions but the new saves to work perfectly. Here is my code for handling the error cases:
private static void SendAncestorResponseCallback(IAsyncResult result)
{
var info = (SendAncestorInfo)result.AsyncState;
try
{
var response = info.Request.EndGetResponse(result);
info.Response = response;
}
catch ( Exception ex)
{
info.Error = ex;
}
info.MainThreadContext.Post(SendAncestorMainThreadCallback, info);
}
private static void SendAncestorMainThreadCallback(object state)
{
var info = (SendAncestorInfo)state;
IAncestor origAncestor = info.Content;
HttpWebResponse response = null;
if (info.Error != null)
{
if ((info.Error as WebException) == null)
{
info.Callback(false, origAncestor, null, info.Error);
return;
}
else //get response from WebException
{
response = (HttpWebResponse)(info.Error as WebException).Response;
}
}
else //get response from info.Response
{
response = info.Response as HttpWebResponse;
}
if (response.StatusCode == HttpStatusCode.Created || response.StatusCode == HttpStatusCode.Forbidden)
{
var stream = response.GetResponseStream();
using (var reader = new StreamReader(stream))
{
IAncestor retAncestor = XMLSerializerHelper.DeserializeObject<Ancestor>(reader.ReadToEnd());
info.Callback(response.StatusCode == HttpStatusCode.Created, origAncestor, retAncestor, null);
}
}
else info.Callback(false, origAncestor, null, info.Error);
}
considering how the web service is written I should only expect http status codes of created or forbidden and like I said with small groups this is the case. The fact that I only start getting the errors mentioned earlier makes me feel like I am doing something wrong with the HttpWebRequest objects etc. Any assistance would be greatly appreciated. Thanks.
--update here is the code that generates the HttpWebRequest:
foreach (IAncestor ancestor in ancestors)
{
AncestorViewModel ancestorVM = new AncestorViewModel(ancestor);
ancestorVM.Status = SaveStatus.Undefined;
ParsedAncestors.Add(ancestorVM);
_service.CreateAncestor(UserSrc, ancestor, (success, origAncestor, retAncestor, exception) =>
{
AncestorViewModel result = ParsedAncestors.First(a => a.Model.IdNo == origAncestor.IdNo);
if (exception == null)//web response was either Created or Forbidden
{
if (success)//Ancestor successfully created
{
savedAncestors++;
SuccessMessage = string.Format("{0} Saved\n", savedAncestors);
result.Status = SaveStatus.Saved;
}
else //Ancestor already existed
{
conflictAncestors.Add(origAncestor, retAncestor);
ConflictMessage = string.Format("{0} Conflicts\n", conflictAncestors.Count);
result.Status = SaveStatus.Conflicted;
}
}
else //Show exception recieved from remote web service
{
//if (exception as WebException != null)
//{
// //if exception is WebException get status code and description
// HttpWebResponse rs = (HttpWebResponse)(exception as WebException).Response;
// Message += string.Format("WebServer returned status code {0}: '{1}'\n", (int)rs.StatusCode, rs.StatusDescription);
//}
errors.Add(origAncestor, exception);
ErrorMessage = string.Format("{0} Errors\n", errors.Count);
result.Status = SaveStatus.Error;
}
});
}
public void CreateAncestor(string userSrc, IAncestor ancestor, Action<bool, IAncestor, IAncestor, Exception> callback)
{
WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);
var request = (HttpWebRequest)WebRequest.Create(
new Uri(string.Format("{0}/{1}/{2}", rootUri, AncestorsRestPoint, userSrc)));
request.Method = "POST";
request.ContentType = "application/xml";
var info = new SendAncestorInfo
{
Request = request,
Callback = callback,
Content = ancestor,
MainThreadContext = SynchronizationContext.Current
};
request.BeginGetRequestStream(SendAncestorRequestCallback, info);
}

Related

Http Post Flutter to SAP

Im trying to use http post to transfer data from flutter to SAP. I can get data without any problem, but post attempt is failing with code 403 (x-csrf-token invalid)
I had the same problem while working in C# but that was resolved using event handler, that triggers just before save (please see below extract of C# code) but i'm unable to find option in flutter. Please guide..
zZSSALE1SRVEntity.SendingRequest2 += new EventHandler<SendingRequest2EventArgs>(_container_SendingRequest_Enhance);
zZSSALE1SRVEntity.SaveChanges();
private void _container_SendingRequest_Enhance(object sender, SendingRequest2EventArgs e)
{
HttpWebResponse response;
string empty = string.Empty;
string str = string.Empty;
CookieContainer cookieContainer = new CookieContainer();
OdataSsaleDEV.ZZSSALE1_SRV_Entities zZSSALE1SRVEntity = new OdataSsaleDEV.ZZSSALE1_SRV_Entities(app_uri)
{
Credentials = credentials
};
string str1 ;
if (empty == string.Empty)
{
HttpWebRequest credentials = (HttpWebRequest)WebRequest.Create(zZSSALE1SRVEntity.BaseUri);
credentials.Method = "GET";
credentials.Headers.Add("X-CSRF-Token", "Fetch");
credentials.Credentials = zZSSALE1SRVEntity.Credentials;
cookieContainer = new CookieContainer();
credentials.CookieContainer = cookieContainer;
try
{
response = (HttpWebResponse)credentials.GetResponse();
}
catch (WebException webException)
{
MessageBox.Show(webException.Message);
return;
}
catch (Exception exception)
{
MessageBox.Show(exception.Message);
return;
}
empty = response.Headers.Get("X-CSRF-Token");
str = response.Headers.Get("Set-Cookie");
credentials.Abort();
}
if (empty != string.Empty)
{
e.RequestMessage.SetHeader("x-csrf-token", empty);
foreach (Cookie cooky in cookieContainer.GetCookies(zZSSALE1SRVEntity.BaseUri))
{
str1 = string.Concat(str1, ";", cooky.ToString());
}
e.RequestMessage.SetHeader("Cookie", str1.Substring(1));
}
Issue resolved.
Actually server requires session cookies (MYSAPSSO and SAP_SESSIONID) along with x-csrf-token.

Why multi node config server fetch the configuration from the same node each time

There are three nodes running in the configuration center, when the server starts, i found that
the server fetch configuration from the same config-server each time, and only the config-server
processes the request.
My server start log:
Multiple Config Server Urls found listed.
Fetching config from server at: http://***:20001/
Located environment: name=onip-boot, profiles=[prod], label=null, version=..., state=null
I read the source code of ConfigServicePropertySourceLocator.java, at line 205, I found it always seems to ask the first to get the configuration and then return, unless it fails and loops the next one. So, Spring Cloud Config does not have the ability of load balancing?
for (int i = 0; i < noOfUrls; i++) {
Credentials credentials = properties.getCredentials(i);
String uri = credentials.getUri();
String username = credentials.getUsername();
String password = credentials.getPassword();
logger.info("Fetching config from server at : " + uri);
try {
HttpHeaders headers = new HttpHeaders();
addAuthorizationToken(properties, headers, username, password);
if (StringUtils.hasText(token)) {
headers.add(TOKEN_HEADER, token);
}
if (StringUtils.hasText(state) && properties.isSendState()) {
headers.add(STATE_HEADER, state);
}
final HttpEntity<Void> entity = new HttpEntity<>((Void) null, headers);
response = restTemplate.exchange(uri + path, HttpMethod.GET, entity,
Environment.class, args);
}
catch (HttpClientErrorException e) {
if (e.getStatusCode() != HttpStatus.NOT_FOUND) {
throw e;
}
}
catch (ResourceAccessException e) {
logger.info("Connect Timeout Exception on Url - " + uri
+ ". Will be trying the next url if available");
if (i == noOfUrls - 1)
throw e;
else
continue;
}
if (response == null || response.getStatusCode() != HttpStatus.OK) {
return null;
}
Environment result = response.getBody();
return result;
}

Best practices to handle Web API status codes

I have a web API project done with .NETCore.
My web API receives a request from another Service A, with the information I have I need to do some conversion on the data and send it to another Service B.
I am expecting that Service B send back some response: like OK or NOK. As the number of codes I can get back from Service B are so much. I would like to know which is the best practices to handle those codes?
As you will see in my code, I get the status code in this way:
var status = (int)response.StatusCode;
And the I have some if to handle this. Looking at my code it looks like a very poor status code Handling but at moment it is the best I can do. I am kindly asking suggestions to improve this.
I am using RestSharp.
Following my code:
[HttpPost]
[Produces("application/json", Type = typeof(MyModel))]
public async Task<IActionResult> Post([FromBody]MyModel myModel)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var response = (RestResponse) await _restHelper.GetResponse("ServiceB:url", myModel);
if (response != null)
{
var status = (int)response.StatusCode;
//2xx status OK
if (status >= 200 && status < 300)
{
return Ok(response.Content);
}
//Catch all status code
return StatusCode(status, response.Content);
}
//If for some reason, I don't get any response from ServiceB
return NotFound("No response from ServiceB");
}
catch (Exception ex)
{
_logger.LogError("POST_ERROR", "ServiceB-relay/Post UNEXPECTED ERROR", ex.Message);
return StatusCode(500, "Server error, not able to process your request");
}
}
and this is my restHelper
public class RestHelper: IRestHelper
{
private readonly IConfigurationRoot _config;
public RestHelper(IConfigurationRoot config)
{
_config = config;
}
public async Task<IRestResponse> GetResponse(string configKey, object dtoObject)
{
//Get the URL from the config.json
var url = _config[configKey];
//Create rest client and rest request
var restClient = new RestClient(url);
var request = new RestRequest {Timeout = 30000, Method = Method.POST};
//Add header
request.AddHeader("Accept", "application/json");
//convert the dto object to json
var jsonObject = JsonConvert.SerializeObject(dtoObject.ToString(), Formatting.Indented);
request.AddParameter("application/json", jsonObject, ParameterType.RequestBody);
var taskCompletion = new TaskCompletionSource<IRestResponse>();
//Execute async
restClient.ExecuteAsync(request, r => taskCompletion.SetResult(r));
//await the task to finish
var response = (RestResponse) await taskCompletion.Task;
return response;
}
Thanks

Vert.x: Blocking handler issue

I want to use blocking handler, but still get an error:
java.lang.IllegalStateException: Response has already been written
Here is my code:
Server.java
r.route("/api/details/send/").handler(BodyHandler.create());
r.route("/api/details/send/").handler(ctx-> {
JsonArray ja = ctx.getBodyAsJsonArray();
JsonArray params = new JsonArray();
vertx.executeBlocking(futur -> {
for(int i =0; i<ja.size();i++) {
JsonObject req = new JsonObject();
req.put("QUERY", "INSERT INTO detailsfacture VALUES ('',?,?,?,?,?,?,?)");
req.put("DB", "MYSQL_");
params.add(ja.getJsonObject(i).getValue("typefacture"))
.add(ja.getJsonObject(i).getValue("activites"))
.add(Integer.parseInt(ja.getJsonObject(i).getValue("qte").toString()))
.add(Double.parseDouble(ja.getJsonObject(i).getValue("pu").toString())
.add(ja.getJsonObject(i).getValue("unite"))
.add(Double.parseDouble(ja.getJsonObject(i).getValue("montant").toString())
.add(ja.getJsonObject(i).getValue("codefacture"));
req.put("PARAMS", params);
eb.send("EXECUTE", req, res -> {
if (res.succeeded()) {
params.clear();
ctx.response().putHeader("content-type", "application/json").end(res.result().body().toString());
} else {
ctx.response().putHeader("content-type", "application/json").end(res.cause().getMessage());
}
});
}
String result = "orsys";
futur.complete(result);
},resultat->{
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, "text/plain");
//resultat.result().toString();
});
});
MySql.java
eb.consumer("MYSQL_EXECUTE_WITH_PARAMS", req->{
try{
JsonObject reqParams = (JsonObject)req.body();
String sql = reqParams.getString("QUERY");
client.getConnection( connection -> {
if (connection.succeeded()) {
try{
SQLConnection con = connection.result();
con.updateWithParams(sql,reqParams.getJsonArray("PARAMS"), query -> {
if(query.succeeded()){
UpdateResult urs = query.result();
req.reply(urs.toJson());
//req.reply(query.result());
}else{
req.fail(24, "Err Request : "+query.cause().getMessage());
}
});
}catch(Exception e){
req.fail(24, "Err Conn Failed : "+e.getMessage());
}
} else {
req.fail(24, "Err No Connection : "+connection.cause().getMessage());
}
});
}catch(Exception e){
req.fail(24, e.getMessage());
}
});
P.S. : When I remove executeBlocking only the first records is registred in my database.
Regards.
You insert entities into detailsfacture in a loop. For each insert you call following:
ctx.response().putHeader("content-type", "application/json").end(res.result().body().toString());
As you can see you call the end(...) method of the response object. Thats where the IllegalStateException comes from. As the documentation states:
Once the response has ended, it cannot be used any more.
So you problem has nothing to do with the executeBlocking.
You should take a look at the write(...) method of HttpServerResponse. For each insert you should call write(...) instead of end(...). But this will only work if you know the complete length of the whole response because you need to set the header Content-length. If you are finished with all inserts you need to call end() to complete the response. Also you should only set the header once and not for each insert.
Now some additional comments. I don't see the need for executeBlocking in your case. Because of the problem with Content-length I recommend to wrap each insert with a Future and compose all of them with CompositeFuture. The Future futur is used the wrong way. The send(...) method of Event bus is not blocking and asynchronous. So the futur.complete(result) is called right after you send all your inserts. Also it's strange that the consumer consumes MYSQL_EXECUTE_WITH_PARAMS and the send sends to EXECUTE.
I tried another solution to get my query like that (?,?,...,?),(?,?,...,?),..,(?,?,...,?).
Here is my code :
public static String getMultipleInsertReq(String table, JsonArray columns,JsonArray data){
JsonObject tab= Tables.Tables_list.getJsonObject(table); // name of table
String sql = "";
if(tab != null){
sql = "INSERT INTO "+table + "( ";
if(columns == null){
columns = tab.getJsonArray("COLS"); //columns from ur database
}
if(columns!=null){
for(int i=0;i<columns.size();i++){
if(i==columns.size()-1){
sql+=columns.getString(i)+") VALUES";
}
else{
sql+=columns.getString(i)+",";
}
}
for(int i =0; i<data.size();i++){
for(int j=0; j<columns.size();j++){
if(j==columns.size()-1 && i!=data.size()-1){
sql+="?),";
}
else if (i==data.size()-1 && j==columns.size()-1){
sql+="?)";
}
else if (j==0){
sql+="(?,";
}
else{
sql+="?,";
}
}
}
return sql;
}
}
return null;
}
Hope it helps.
P.S.: it's only a query builder so you can adapt it depending on your needs.
Regards.

Why is the HttpWebRequest body val null after "crossing the Rubicon"?

I am trying to send the contents of an XML file from a handheld device (Compact Framework/Windows CE) to a Web API method in my server app like so (Client code):
public static string SendXMLFile(string xmlFilepath, string uri, int timeout)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.KeepAlive = false;
request.ProtocolVersion = HttpVersion.Version10;
request.Method = "POST";
StringBuilder sb = new StringBuilder();
using (StreamReader sr = new StreamReader(xmlFilepath))
{
String line;
while ((line = sr.ReadLine()) != null)
{
// test to see if it's finding any lines
//MessageBox.Show(line); <= works fine
sb.AppendLine(line);
}
byte[] postBytes = Encoding.UTF8.GetBytes(sb.ToString());
if (timeout < 0)
{
request.ReadWriteTimeout = timeout;
request.Timeout = timeout;
}
request.ContentLength = postBytes.Length;
request.KeepAlive = false;
request.ContentType = "application/xml";
try
{
Stream requestStream = request.GetRequestStream();
requestStream.Write(postBytes, 0, postBytes.Length);
requestStream.Close();
using (var response = (HttpWebResponse)request.GetResponse())
{
return response.ToString();
}
}
catch (Exception ex)
{
MessageBox.Show("SendXMLFile exception " + ex.Message);
request.Abort();
return string.Empty;
}
}
}
As you can see in the commented out code ("<= works fine"), I've tested it and the data I want is being added to the StringBuilder. There is no exception being thrown (I don't see "SendXMLFile exception ").
However, when the corresponding server code is called:
[Route("api/DeliveryItems/PostArgsAndXMLFileAsStr")]
public async void PostArgsAndXMLFileAsStr([FromBody] string stringifiedXML, string serialNum, string siteNum)
{
string beginningInvoiceNum = string.Empty;
string endingInvoiceNum = string.Empty;
XDocument doc = XDocument.Parse(stringifiedXML);
...the "serialNum" and "siteNum" args are as expected (contain the valid expected values) but the body (stringifiedXML) is null. Why?
UPDATE
I added this, too, in the client:
request.ContentLength = postBytes.Length;
// Did the sb get into the byte array?
MessageBox.Show(request.ContentLength.ToString());
...and the byte array does have the data, as it shows me "112" (the XML file is quite small).
UPDATE 2
Now I added yet another debug msg:
try
{
Stream requestStream = request.GetRequestStream();
// now test this:
MessageBox.Show(string.Format("requestStream length is {0}", requestStream.Length.ToString()));
requestStream.Write(postBytes, 0, postBytes.Length);
requestStream.Close();
using (var response = (HttpWebResponse)request.GetResponse())
{
return response.ToString();
}
}
catch (Exception ex)
{
MessageBox.Show("SendXMLFile exception " + ex.Message);
request.Abort();
return string.Empty;
}
...and I don't even see the "requestStream length is" message; instead I see, "SendXMLFileException NotSupportedException"...???
UPDATE 3
I guess this is an example of the Hawthorn Effect or similar. Once I commented out that debug (MessageBox.Show()) statement, I'm back to making it into the server app, but with the [FromBody] val null.
Then the client has the message, "Unable to read data from the transport connection"
UPDATE 4
stringifiedXML is still null here:
public async void PostArgsAndXMLFileAsStr([FromBody] string stringifiedXML, string serialNum, string siteNum)
{
string beginningInvoiceNum = string.Empty;
string endingInvoiceNum = string.Empty;
XDocument doc = XDocument.Parse(stringifiedXML);
...even after I modified the code in the client following a response to this question like so:
public static string SendXMLFile(string xmlFilepath, string uri)
{
MessageBox.Show(string.Format("In SendXMLFile() - xmlFilepath == {0}, uri == {1}", xmlFilepath, uri));
string strData = GetDataFromXMLFile();
HttpWebRequest request = CreateRequest(uri, HttpMethods.POST, strData, "application/xml");
request.KeepAlive = false;
request.ProtocolVersion = HttpVersion.Version10;
try
{
using (var response = (HttpWebResponse)request.GetResponse())
{
return response.GetResponseStream().ToString();
}
}
catch (Exception ex)
{
MessageBox.Show("SendXMLFile exception " + ex.Message);
request.Abort();
return string.Empty;
}
}
private static string GetDataFromXMLFile()
{
// test data - if it works, get the (same, for now) data from the file
return #"<?xml version=1.0?><LocateAndLaunch><Tasks></Tasks><Locations></Locations></LocateAndLaunch>"; //had to remove "s from version num
}
// Based on code from Andy Wiggly (the owner of Wiggly Field in Chicago and the Wiggly chewing gum company?)
public static HttpWebRequest CreateRequest(string uri, HttpMethods method, string data, string contentType)
{
WebRequest request = HttpWebRequest.Create(uri);
request.Method = Enum.ToObject(typeof(HttpMethods), method).ToString();
request.ContentType = contentType;
((HttpWebRequest)request).Accept = contentType;
if (method != HttpMethods.GET && method != HttpMethods.DELETE)
{
Encoding encoding = Encoding.UTF8;
request.ContentLength = encoding.GetByteCount(data);
request.ContentType = contentType;
request.GetRequestStream().Write(
encoding.GetBytes(data), 0, (int)request.ContentLength);
request.GetRequestStream().Close();
}
else
{
// If we're doing a GET or DELETE don't bother with this
request.ContentLength = 0;
}
// Finally, return the newly created request to the caller.
return request as HttpWebRequest;
}
Note: I don't know if this is just a misleading side-effect of shutting down the server, but I subsequently saw this err msg in the client/handheld app:
"System.Net.ProtocolVi..."
"This operation cannot be performed after the request has been submitted."
UPDATE 5
For those wanting a Stack Trace, &c:
serNum and siteNum are simple values that get concatenated into the uri like so:
string uri = string.Format("http://192.168.125.50:28642/api/FileTransfer/GetHHSetupUpdate?serialNum={0}&clientVersion={1}", serNum, clientVer);
I tried to get the Stack Trace like so:
catch (Exception ex)
{
MessageBox.Show(string.Format("Msg = {0}; StackTrace = {1)", ex.Message, ex.StackTrace));
request.Abort();
return string.Empty;
}
...but now I'm only seeing, "This operation cannot be performed after the request has been submitted."
UPDATE 6
I changed the method signature to this:
public static HttpWebResponse SendXMLFile(string xmlFilepath, string uri)
...and the corresponding code to this:
try
{
using (var response = (HttpWebResponse)request.GetResponse())
{
return response;
}
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Msg = {0}; StackTrace = {1)", ex.Message, ex.StackTrace));
request.Abort();
return null;
}
...but it made no difference (and I see no "StackTrave = " message, so it must be failing erstwheres)
UPDATE 7
I put two debug strings in:
0)
public static HttpWebRequest CreateRequestNoCredentials(string uri, HttpMethods method, string data, string contentType)
{
//test:
MessageBox.Show(string.Format("In CreateRequestNoCredentials(); data passed in = {0}", data));
1) In SendXMLFile():
//test:
MessageBox.Show(string.Format("After calling CreateRequestNoCredentials(), request contentLen = {0}, headers = {1}, requestUri = {2}",
request.ContentLength, request.Headers, request.RequestUri));
...and I see this:
...but then before the second one gets a chance to show me the gory details, the server receives the null body value, crashes thuswith, and then the client whin[g]es with the same old "This operation cannot be performed after the request has been submitted" complaint.
UPDATE 8
In response to the suggestion, "I suspect that if you remove the setting of KeepAlive and ProtocolVersion after the CreateRequest call, the exception will go away.", I changed my code from this:
HttpWebRequest request = CreateRequestNoCredentials(uri, HttpMethods.POST, strData, "application/xml");
//test:
MessageBox.Show(string.Format("After calling CreateRequestNoCredentials(), request contentLen = {0}, headers = {1}, requestUri = {2}",
request.ContentLength, request.Headers, request.RequestUri));
request.KeepAlive = false;
request.ProtocolVersion = HttpVersion.Version10;
public static HttpWebRequest CreateRequestNoCredentials(string uri, HttpMethods method, string data, string contentType)
{
//test:
MessageBox.Show(string.Format("In CreateRequestNoCredentials(); data passed in = {0}", data));
WebRequest request = HttpWebRequest.Create(uri);
request.Method = Enum.ToObject(typeof(HttpMethods), method).ToString();
request.ContentType = contentType;
((HttpWebRequest)request).Accept = contentType;
if (method != HttpMethods.GET && method != HttpMethods.DELETE)
{
Encoding encoding = Encoding.UTF8;
request.ContentLength = encoding.GetByteCount(data);
request.ContentType = contentType;
request.GetRequestStream().Write(
encoding.GetBytes(data), 0, (int)request.ContentLength);
request.GetRequestStream().Close();
}
else
{
// If we're doing a GET or DELETE don't bother with this
request.ContentLength = 0;
}
// Finally, return the newly created request to the caller.
return request as HttpWebRequest;
}
...to this:
HttpWebRequest request = CreateRequestNoCredentials(uri, HttpMethods.POST, strData, "application/xml");
//test:
MessageBox.Show(string.Format("After calling CreateRequestNoCredentials(), request contentLen = {0}, headers = {1}, requestUri = {2}",
request.ContentLength, request.Headers, request.RequestUri));
public static HttpWebRequest CreateRequestNoCredentials(string uri, HttpMethods method, string data, string contentType)
{
//test:
MessageBox.Show(string.Format("In CreateRequestNoCredentials(); data passed in = {0}", data));
WebRequest request = HttpWebRequest.Create(uri);
request.Method = Enum.ToObject(typeof(HttpMethods), method).ToString();
request.ContentType = contentType;
((HttpWebRequest)request).Accept = contentType;
// moved from elsewhere to here:
((HttpWebRequest)request).KeepAlive = false;
((HttpWebRequest)request).ProtocolVersion = HttpVersion.Version10;
if (method != HttpMethods.GET && method != HttpMethods.DELETE)
{
Encoding encoding = Encoding.UTF8;
request.ContentLength = encoding.GetByteCount(data);
request.ContentType = contentType;
request.GetRequestStream().Write(
encoding.GetBytes(data), 0, (int)request.ContentLength);
request.GetRequestStream().Close();
}
else
{
// If we're doing a GET or DELETE don't bother with this
request.ContentLength = 0;
}
// Finally, return the newly created request to the caller.
return request as HttpWebRequest;
}
...and yet I still get that same err msg ("This operation cannot be performed after the request has been submitted") and stringifiedXML is still null when it hits the server.
UPDATE 9
Here is what I get when I send what I understand to be what I should via Fiddler 2 (right-click the image and open in a new tab if you don't have visual superpowers):
...but I don't know what I'm looking at...did it work? Did it fail? The "body == 0" column gives me pause/makes me think it failed, yet "204" seems to mean "The server successfully processed the request, but is not returning any content"...
UPDATE 10
Here is the Fiddler scream shot after fixing the uri, and I do reach the breakpoint in the server app, with good data sent:
UPDATE 11
With changing this code:
string strData = sb.ToString();
HttpWebRequest request = CreateRequestNoCredentials(uri, HttpMethods.POST, strData, "application/xml");
...to this:
string strData = #sb.ToString(); // GetDataFromXMLFile();
string body = String.Format("\"{0}\"", strData);
HttpWebRequest request = CreateRequestNoCredentials(uri, HttpMethods.POST, body, "application/json");
...I'm now getting this in stringifiedXML: "
...and so I'm now getting: "System.Xml.XmlException was unhandled by user code
HResult=-2146232000
Message=Unexpected end of file has occurred. Line 1, position 15..."
It's an improvement, anyway...
UPDATE 12
Depending on the exact makeup/formatting of the string passed as "Request Body" in Fiddle, the results differ radically.
With this as Request Body:
<?xml version="1.0"?><LocateAndLaunch><Tasks></Tasks><Locations></Locations></LocateAndLaunch>
...stringifiedXML is null
With this as Request Body:
"<?xml version=1.0?><LocateAndLaunch><Tasks></Tasks><Locations></Locations></LocateAndLaunch>"
...stringifiedXML is exactly the same ("")
...but there is an exception:
System.Xml.XmlException was unhandled by user code
HResult=-2146232000
Message='1.0' is an unexpected token. The expected token is '"' or '''. Line 1, position 15.
Source=System.Xml
LineNumber=1
LinePosition=15
SourceUri=""
StackTrace:
at System.Xml.XmlTextReaderImpl.Throw(Exception e)
at System.Xml.XmlTextReaderImpl.Throw(String res, String[] args)
at System.Xml.XmlTextReaderImpl.ThrowUnexpectedToken(String expectedToken1, String expectedToken2)
at System.Xml.XmlTextReaderImpl.ParseXmlDeclaration(Boolean isTextDecl)
at System.Xml.XmlTextReaderImpl.Read()
at System.Xml.Linq.XDocument.Load(XmlReader reader, LoadOptions options)
at System.Xml.Linq.XDocument.Parse(String text, LoadOptions options)
at System.Xml.Linq.XDocument.Parse(String text)
at HandheldServer.Controllers.DeliveryItemsController.d__2.MoveNext() in c:\HandheldServer\HandheldServer
\Controllers\DeliveryItemsController.cs:line 63
InnerException:
With this as Request Body:
"<?xml version="1.0"?><LocateAndLaunch><Tasks></Tasks><Locations></Locations></LocateAndLaunch>"
...stringifiedXML is "
Penultimately, with this as Request Body:
"<?xml version=\"1.0\"?><LocateAndLaunch><Tasks></Tasks><Locations></Locations></LocateAndLaunch>"
...stringifiedXML is exactly the same thing ("")
...but I get this exception:
System.InvalidOperationException was unhandled by user code
HResult=-2146233079
Message=Sequence contains no elements
Source=System.Core
StackTrace:
at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
at HandheldServer.Controllers.DeliveryItemsController.d__2.MoveNext() in c:\HandheldServer\HandheldServer\Controllers\DeliveryItemsController.cs:line 109
InnerException:
And finally, if I pass this, with (albeit bogus) vals within the angulars:
"<?xml version=\"1.0\"?><LocateAndLaunch><Tasks>Some Task</Tasks><Locations>Some Location</Locations></LocateAndLaunch>"
...I STILL get "sequence contains no elements"
This method is more picky than Rachel Canning! What does it want - egg in its beer?!?
UPDATE 13
With this code:
public async void PostArgsAndXMLFileAsStr([FromBody] string stringifiedXML, string serialNum, string siteNum)
{
XDocument doc = XDocument.Parse(await Request.Content.ReadAsStringAsync());
...or this:
. . .XDocument doc = XDocument.Load(await Request.Content.ReadAsStreamAsync());
...and this as the incoming stringifiedXML:
"Some TaskSome Location"
...I get the exception:
"System.Xml.XmlException was unhandled by user code
HResult=-2146232000
Message=Root element is missing."
With this code (same stringifiedXML):
XDocument doc = XDocument.Parse(stringifiedXML);
... I get, "System.InvalidOperationException was unhandled by user code
HResult=-2146233079
Message=Sequence contains no elements
Source=System.Core
StackTrace:
at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
at HandheldServer.Controllers.DeliveryItemsController.d__2.MoveNext() in c:\HandheldServer\HandheldServer
\Controllers\DeliveryItemsController.cs:line 109
InnerException: "
IOW, depending on how I parse the incoming string, I get either "Root element is missing" or "Sequence contains no elements"
What the Deuce McAlistair MacLean Virginia Weeper?!? Isn't "<LocateAndLaunch>" a root element? Aren't "Some Task" and "Some Location" elements?
For the action method like this
public async void PostArgsAndXMLFileAsStr([FromBody] string stringifiedXML,
string serialNum, string siteNum)
{}
the request message must be like this. I use JSON here.
POST http://localhost:port/api/values/PostArgsAndXMLFileAsStr?serialNum=1&siteNum=2 HTTP/1.1
Content-Type: application/json
Host: localhost:port
Content-Length: 94
"<?xml version=1.0?><LocateAndLaunch><Tasks></Tasks><Locations></Locations></LocateAndLaunch>"
The request body needs to contain the double quotes, BTW. With this, binding should work correctly.
So, post the message with content type application/json and format the body like this.
string content = #"<?xml version=1.0?><LocateAndLaunch><Tasks></Tasks><Locations></Locations></LocateAndLaunch>";
string body = String.Format("\"{0}\"", content);
Before, you change anything in the client side code, use Fiddler to send a POST like the one above to ensure it works in the web API side. After that, change your client side to make sure it outputs the request just the working request with Fiddler.