I am trying to use ServiceStack to return a file to a ServiceStack client in a RESTful manner.
I have read other questions on SO (here and here) which advise using HttpResult and a FileInfo object or MemoryStream to allow the ContentType header to be changed to the relevant file type.
This works for me when I call the service via a browser, the correct file automatically starts to download. How do I consume the file using one of the ServiceStack clients though?
I'm using a Request DTO and trying to return using something similar to
return new HttpResult(new FileInfo("file.xml"), asAttachment:true) {
ContentType = "text/xml"
};
How would I consume this with the JsonServiceClient for example?
I had a similar requirement which also required me to track progress of the streaming file download. I did it roughly like this:
server-side:
service:
public object Get(FooRequest request)
{
var stream = ...//some Stream
return new StreamedResult(stream);
}
StreamedResult class:
public class StreamedResult : IHasOptions, IStreamWriter
{
public IDictionary<string, string> Options { get; private set; }
Stream _responseStream;
public StreamedResult(Stream responseStream)
{
_responseStream = responseStream;
long length = -1;
try { length = _responseStream.Length; }
catch (NotSupportedException) { }
Options = new Dictionary<string, string>
{
{"Content-Type", "application/octet-stream"},
{ "X-Api-Length", length.ToString() }
};
}
public void WriteTo(Stream responseStream)
{
if (_responseStream == null)
return;
using (_responseStream)
{
_responseStream.WriteTo(responseStream);
responseStream.Flush();
}
}
}
client-side:
string path = Path.GetTempFileName();//in reality, wrap this in try... so as not to leave hanging tmp files
var response = client.Get<HttpWebResponse>("/foo/bar");
long length;
if (!long.TryParse(response.GetResponseHeader("X-Api-Length"), out length))
length = -1;
using (var fs = System.IO.File.OpenWrite(path))
fs.CopyFrom(response.GetResponseStream(), new CopyFromArguments(new ProgressChange((x, y) => { Console.WriteLine(">> {0} {1}".Fmt(x, y)); }), TimeSpan.FromMilliseconds(100), length));
The "CopyFrom" extension method was borrowed directly from the source code file "StreamHelper.cs" in this project here: Copy a Stream with Progress Reporting (Kudos to Henning Dieterichs)
And kudos to mythz and any contributor to ServiceStack. Great project!
You wouldn't consume files with the ServiceStack's .NET ServiceClients as they're mainly for sending DTO's.
You can just use any normal WebRequest to download files, in the v3.9.33 of ServiceStack introduced some handy WebRequest extensions HTTP Utils that make this easy, e.g:
For a text file:
var xmlFile = downloadUrl.GetXmlFromUrl(responseFilter: httpRes => {
var fileInfoHeaders = httpRes.Headers[HttpHeaders.ContentDisposition];
});
Where fileInfoHeaders contains the W3C ContentDisposition HTTP Header, e.g. when returning a FileInfo, ServiceStack returns:
attachment;filename="file.xml";size={bytesLen};
creation-date={date};modification-date={date};read-date={date};
To download a binary file you can use:
var rawBytes = downloadUrl.GetBytesFromUrl(httpRes => ...);
I have found mythz answer to work well, but it is also possible to use their built in JSonServiceClient to also process file requests as well, just in a slightly non-intuitive way because you can't actually use the return type you would expect.
For a model definition like this:
[Route("/filestorage/outgoing/{Name}.{Extension}", "GET")]
[Route("/filestorage/outgoing", "GET")]
public class GetFileStorageStream : IReturn<HttpResult>
{
public string Name { get; set; }
public string Extension { get; set; }
public bool ForDownload { get; set; }
}
You can define your service to return an HttpResult:
public class FileStorageService : Service
{
public HttpResult Get(GetFileStorageStream fileInformation)
{
var internalResult = GetFromFileStorage(fileInformation);
var fullFilePath = Path.Combine("C:\Temp", internalResult.FileName);
return new HttpResult(new FileInfo(fullFilePath), asAttachment: fileInformation.ForDownload);
}
}
Then on the client side you can use this Get template to properly get the web context:
var client = new JsonServiceClient("http://localhost:53842");
var httpResponse = client.Get<HttpWebResponse>("/filestorage/outgoing/test.jpg");
pictureBox1.Image = Image.FromStream(httpResponse.GetResponseStream());
I found it was not possible to use the new API Get methods as they would attempt to deserialize the HttpResult which isn't actually a true return type but a class representing the web context that service stack has created.
You can intercept the response prior to it being handled by using a response filter, like below:
ServiceClientBase.HttpWebResponseFilter = response =>
{
if (response.Headers["Content-Disposition"] != null)
{
var t = response.DownloadText();
Console.WriteLine(t);
}
};
However, this is not the best way to handle it, since the actual call to client.Method() will result in an ArgumentException when the client attempts to read the response stream (since it has been read previously by response.DownloadFile(...). I haven't yet figured out a way to handle it gracefully, but I 'll update my answer if I do.
Related
I am trying to get a list of 100 help desk tickets from an API. the url is below.
https://sdpondemand.manageengine.com/app/itdesk/api/v3/requests?input_data={"list_Info":{"row_count":100,"start_index":101}}
I am able to produce successful results, meaning it brings back 100 rows starting at index 101, when I put the uri into a string like this:
string extra = "app/itdesk/api/v3/requests?input_data={\"list_info\":{\"row_count\":100,\"start_index\":101}}";
but if I try to put the json into classes, then serialize it with the Json.Net library, it will fail, meaning it brings back just 10 rows on index 1.
private class input_data
{
public list_info list_Info = new list_info();
}
private class list_info
{
public int row_count = 100;
public int start_index = 101;
}
input_data input = new input_data();
string json = Newtonsoft.Json.JsonConvert.SerializeObject(input);
string extra1 ="app/itdesk/api/v3/requests?input_data="+json;
I look at both of the request coming out an there exactly the same. what am I doing wrong?
what the vars look like in the code
extra: app/itdesk/api/v3/requests?input_data={"list_info":{"row_count":100,"start_index":101}}
extra1: app/itdesk/api/v3/requests?input_data={"list_Info":{"row_count":100,"start_index":101}}
Passing serialized DTO object in the Get request is not the proper way of implementing an API. Get request supposed to be having params rather than a serialize object. If you wish to do so and have to send an object then why not using a post request.
The sample implementation for a rest api could be as:
Via GET
[Route("{rowCount}/{startIndex}"), HttpGet]
public IHttpActionResult Get(int rowCount, int startIndex)
{
//Your logic Implementation
}
calling would be like
www.xyz.com/controllerName/100/101
This is the rest implementation of the request
Via POST
[Route(""), HttpPost]
public IHttpActionResult Post([FromBody]YourDTOClass obj)
{
//Your logic Implementation
}
For example you have the DTO class
//In C# the class name should be capital
private class ListInfo
{
//In c# the property name should be Capital
public int RowCount {get; set;} = 100;
public int StartIndex {get; set;}= 101;
}
So your Post method would look like
//Route attribute is for configuring the custom route
//It is a feature in MVC 5
//FromBody attribute will search for data in the request body
[Route(""), HttpPost]
public IHttpActionResult Post([FromBody]ListInfo info)
{
//Your logic Implementation
}
If you are using the C# for calling the API too, then you could use HttpClient where passing the json object of your class a data.
Edited: As you are using a third party API, therefore you need to correct the calling.
using (var client = new HttpClient())
{
//Setting the base address of the server
client.BaseAddress = new Uri("https://sdpondemand.manageengine.com");
//creating an anonymous object
var jsonObject = new {
input_data = new {
row_count = 100,
start_index = 101
}
};
//Converting into the content string
var content = new StringContent(JsonConvert.SerializeObject(jsonObject), Encoding.UTF8, "application/json");
//waiting for the post request to complete
var result = await client.PostAsync("app/itdesk/api/v3/requests", content);
//reading the response string
string resultContent = await result.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
//Deserialize your string into custom object here
var obj = JsonConvert.DeserializeObject<YourDTO>(resultContent);
}
else
{
//Todo: Log the Exception here
throw new Exception(contentString);
}
}
extra : app/itdesk/api/v3/requests?input_data={"list_info"{"row_count":100,"start_index":101}}
extra1:app/itdesk/api/v3/requests?input_data={"list_Info":{"row_count":100,"start_index":101}}
messed up the list info
extra : app/itdesk/api/v3/requests?input_data={"list_info"{"row_count":100,"start_index":101}}
extra1:app/itdesk/api/v3/requests?input_data={"list_info":{"row_count":100,"start_index":101}}
[shrug emoji]
Adding comments to this to clear up confusion. ManageEngine Service Desk Plus API is not a properly/typically implemented API. Basically you have to URL encode JSON and pass it in either the URL as a param or in the form as a application/x-www-form-urlencoded
example:
json:
{
"request": {
"subject": "testing subject",
"description": "I am a test. Delete me.",
"requester": {
"id": "4817"
},
"subcategory": {
"name": "Errors/Problems Using SalesForce",
"id": "2406"
},
"category": {
"name": "SalesForce",
"id": "608"
}
}
}
becomes:
input_data='%7B%0A++%22request%22%3A+%7B%0A++++%22subject%22%3A+%22testing+subject%22%2C%0A++++%22description%22%3A+%22I+am+a+test.+Delete+me.%22%2C%0A++++%22requester%22%3A+%7B%0A++++++%22id%22%3A+%224817%22%0A++++%7D%2C%0A++++%22subcategory%22%3A+%7B%0A++++%22name%22%3A+%22Errors%2FProblems+Using+SalesForce%22%2C%0A++++%22id%22%3A+%222406%22%0A++++%7D%2C%0A++++%22category%22%3A+%7B%0A++++%22name%22%3A+%22SalesForce%22%2C%0A++++%22id%22%3A+%22608%22%0A++++%7D%0A++%7D%0A%7D'
I am using flurl to submit HTTP request and this is very useful. Now I need to change the "Content-Type" header for some of the requests to "application/json;odata=verbose"
public async Task<Job> AddJob()
{
var flurlClient = GetBaseUrlForGetOperations("Jobs").WithHeader("Content-Type", "application/json;odata=verbose");
return await flurlClient.PostJsonAsync(new
{
//Some parameters here which are not the problem since tested with Postman
}).ReceiveJson<Job>();
}
private IFlurlClient GetBaseUrlForOperations(string resource)
{
var url = _azureApiUrl
.AppendPathSegment("api")
.AppendPathSegment(resource)
.WithOAuthBearerToken(AzureAuthentication.AccessToken)
.WithHeader("x-ms-version", "2.11")
.WithHeader("Accept", "application/json");
return url;
}
You can see how I tried to add the header above (.WithHeader("Content-Type", "application/json;odata=verbose"))
Unfortunately this gives me following error:
"InvalidOperationException: Misused header name. Make sure request
headers are used with HttpRequestMessage, response headers with
HttpResponseMessage, and content headers with HttpContent objects."
I also tried flurl's "ConfigureHttpClient" method but could not find how/where to set the content type header.
This answer is outdated. Upgrade to latest version (2.0 or above) and the problem goes away.
It turns out the real issue has to do with how the System.Net.Http APIs validate headers. It makes a distinction between request-level headers and content-level headers, which I've always found a bit odd since raw HTTP makes no such distinction (except perhaps in multipart scenarios). Flurl's WithHeader adds headers to the HttpRequestMessage object but is failing validation for Content-Type, which it expects to be added to the HttpContent object.
Those APIs do allow you to skip validation, and although Flurl doesn't expose it directly, you can get under the hood pretty easily, without breaking the fluent chain:
return await GetBaseUrlForGetOperations("Jobs")
.ConfigureHttpClient(c => c.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json;odata=verbose"))
.PostJsonAsync(new { ... })
.ReceiveJson<Job>();
This is probably the best way to do what you need and still take advantage of Flurl's goodness, i.e. not have to directly deal with serialization, HttpContent objects, etc.
I'm strongly considering changing Flurl's AddHeader(s) implementations to use TryAddWithoutValidation based on this issue.
The comments and another post I found (will add reference when I find it again) have pointed me to the right direction.
The solution for my problem looks like:
var jobInJson = JsonConvert.SerializeObject(job);
var json = new StringContent(jobInJson, Encoding.UTF8);
json.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; odata=verbose");
var flurClient = GetBaseUrlForOperations("Jobs");
return await flurClient.PostAsync(json).ReceiveJson<Job>();
Edit: Found the related SO question: Azure encoding job via REST Fails
public static class Utils
{
public static IFlurlClient GetBaseUrlForOperations(string resource)
{
var _apiUrl = "https://api.mobile.azure.com/v0.1/apps/";
var url = _apiUrl
.AppendPathSegment("Red-Space")
.AppendPathSegment("HD")
.AppendPathSegment("push")
.AppendPathSegment("notifications")
.WithHeader("Accept", "application/json")
.WithHeader("X-API-Token", "myapitocken");
return url;
}
public static async Task Invia()
{
FlurlClient _client;
PushMessage pushMessage = new PushMessage();
pushMessage.notification_content = new NotificationContent();
try
{
var flurClient = Utils.GetBaseUrlForOperations("risorsa");
// News news = (News)contentService.GetById(node.Id);
//pushMessage.notification_target.type = "";
pushMessage.notification_content.name = "A2";
// pushMessage.notification_content.title = node.GetValue("TitoloNews").ToString();
pushMessage.notification_content.title = "Titolo";
pushMessage.notification_content.body = "Contenuto";
var jobInJson = JsonConvert.SerializeObject(pushMessage);
var json = new StringContent(jobInJson, Encoding.UTF8);
json.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
dynamic data2 = await flurClient.PostAsync(json).ReceiveJson();
var expandoDic = (IDictionary<string, object>)data2;
var name = expandoDic["notification_id"];
Console.WriteLine(name);
}
catch (FlurlHttpTimeoutException ex)
{
Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType + " " + ex);
}
catch (FlurlHttpException ex)
{
Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType + " " + ex);
if (ex.Call.Response != null)
Console.WriteLine("Failed with response code " + ex.Call.Response.StatusCode);
else
Console.WriteLine("Totally failed before getting a response! " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType + " " + ex);
}
}
}
public class NotificationTarget
{
public string type { get; set; }
}
public class CustomData {}
public class NotificationContent
{
public string name { get; set; }
public string title { get; set; }
public string body { get; set; }
public CustomData custom_data { get; set; }
}
public class PushMessage
{
public NotificationTarget notification_target { get; set; }
public NotificationContent notification_content { get; set; }
}
Am I allowed to post 3 answers to the same question? :)
Upgrade. Flurl.Http 2.0 includes the following enhancements to headers:
WithHeader(s) now uses TryAddWithoutValidation under the hood. With that change alone, the OP's code will work as originally posted.
Headers are now set at the request level, which solves another known issue.
When using SetHeaders with object notation, underscores in property names will be converted to hyphens in the header names, since hyphens in headers are very common, underscores are not, and hyphens are not allowed in C# identifiers.
This will be useful in your case:
.WithHeaders(new {
x_ms_version = "2.11",
Accept = "application/json"
});
I'm not an OData expert and I don't know what API you're calling (SharePoint?), but based on most examples I've seen, what you typically want to do is ask the server to send verbose OData in the response, rather than declare that you're sending it in the request. In other words, you want to set the ;odata=verbose bit on the Accept header, not Content-Type. application/json should be good enough for Content-Type, and Flurl will set that for you automatically, so just try this change and see if it works:
.WithHeader("Accept", "application/json;odata=verbose");
Good morning! I'm still learning my way around REST API calls and generally processing HTTP requests, so please provide any feedback :)
I'm trying to make a GET call to a web service, which should return a JSON string that's a set of information, which is supposed to be a SQL record returned in a sort of dictionary-like manner.
However, I am having several hiccups and don't know if I'm approach the resolution correctly:
Call web service. Define it as a 'GET' call.
Process the response and read it using a StreamReader.
Using the NewtonSoft JSON method, deserialize the response into a dictionary format. <-- Currently stuck here.
Use dictionary to my liking.
// Call the Web Service
string url = "{URL HERE}";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.ContentType = "application/json";
request.ContentLength = 0;
//Dictionary<string, string> dataList = null;
try
{
WebResponse response = request.GetResponse();
Stream responseStream = response.GetResponseStream();
StreamReader reader = new StreamReader(responseStream, Encoding.UTF8);
var json = reader.ReadToEnd();
//Dictionary<string, string> values = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
var data = JsonConvert.DeserializeObject<List<KeyValuePair<string, List<KeyValuePair<string, string>>>>>(json);
var dataDictionary = data.ToDictionary(
x => x.Key,
x => x.Value.ToDictionary(y => y.Key, y => y.Value));
//dataList = (Dictionary<string, string>) dataDictionary;
var test = dataDictionary["Key"];
What the JSON string from the call sort of looks like:
"[{\"Number\":4,\"FieldABC\":\"ABC\"}]"
Other solutions I have tried:
Using the data type Dictionary instead.
Various other method calls, like the ones commented out in the code.
Another SO post suggested the ToDictionary() call.
Errors I have run into:
{"Value cannot be null.\r\nParameter name: source"} System.Exception {System.ArgumentNullException}
Cannot convert JSON string to JSON object.
Am I misunderstanding how to process the response from the HTTP request? Or am I confusing / misusing data types? Any help is greatly appreciated!
Update: With all the answers below, the solution ended up being a combination of all of the comments! First, I used json2csharp.com to create the object. Then, using the updated HTTP calls recommended below, I deserialized the JSON into a type Lists so that I got a list of length 1 that I could access my information from. Thanks everyone!
So when I use http://json2csharp.com/ for example to use your JSON, I would get:
public class ClassName
{
public int Number { get; set; }
public string FieldABC { get; set; }
}
Use HttpClient, that makes it easier:
using (var client = new HttpClient())
{
var response = await client.GetAsync(progressUrl);
var yourObject == JsonConvert.DeserializeObject<ClassName>(await response .Content.ReadAsStringAsync());
}
Continue with the rest...
You can also use HttpClientExtensions Class.
Cheers!
First make a POCO which exactly matches the response string. Something like this:
public class MyResponseObject {
public int Number { get; set; }
public string FieldABC { get; set; }
// other properties here
}
Assuming you are sent an array of these objects as [{\"Number\":4,\"FieldABC\":\"ABC\"}] suggests then you can deserialize it using
JsonConvert.DeserializeObject<List<MyResponseObject>>(json);
If it is a more complicated object then you can include additional nested objects in your principal object:
public class MyNestedObject {
public string NestedName { get; set; }
public bool IsEnabled { get; set; }
}
public class MyResponseObject {
public List<MyNestedObject> NestedObjects { get; set; }
}
Once you have a List<MyResponseObject> you may find you don't need a dictionary at all, or if you do you can take advantage of the many Linq Enumerable methods without worrying about JSON.
Use HttpClient instead of HttpWebRequest, as follows:
using (var httpClient = new System.Net.Http.HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
var response = await httpClient.GetAsync(url);
var data = await response.Content.ReadAsStringAsync();
Dictionary<string, string> ValueList = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
}
I need to call this method in MVC controller and pass the UpdateRequest object as json format. how I can do that?
[HttpPost]
[Route("updatecertificate")]
public void updatecertificate([FromBody] UpdateRequest certificatereviewed)
{
loansRepository.updatecertificate(certificatereviewed.Id, certificatereviewed.CertificateReview);
}
and this is the input class:
public class UpdateRequest {
public int Id { get; set; }
public bool CertificateReview { get; set;}
}
this is how I call and send separate variable but now I like to send the class object in json format.
private async Task UpdateFundingCertificateReviewed(int id, bool fundingCertificateReviewed)
{
await httpClient.PostAsync(string.Format("{0}/{1}", LoanApiBaseUrlValue, updatecertificate),null);
}
I personally like Newtonsoft.Json to serialize the object.
private async Task UpdateFundingCertificateReviewed
(int id, bool fundingCertificateReviewed)
{
using (var client = new HttpClient())
{
var url = string.Format("{0}/{1}", LoanApiBaseUrlValue, updatecertificate);
var updateRequest = new UpdateRequest { Id = 1, CertificateReview = true};
var data = JsonConvert.SerializeObject(updateRequest);
await client.PostAsync(url, data);
}
}
FYI: Async without return is not a good practice. However, it is out of the original question.
If you want to transform an object into a JSON string, see this question: Turn C# object into a JSON string in .NET 4
var json = new JavaScriptSerializer().Serialize(obj);
Is this what you are after or do you want to know how to construct the http request with a JSON object in the body?
your questionis not very clear, what is the outcome that you expect ?
If you want to POST an request with JSON body you can check the #Win comment,
however if you want to make an Response from the Api to the MVC project you should do a bit more steps tough. :))
I am transforming HttpContent into the following dto:
public class ContentDto
{
public string ContentType {get; set;}
public string Headers {get; set; }
public object Data { get; set; }
public ContentDto(HttpContent content)
{
Headers = content.Headers.Flatten();
// rest of the setup
}
}
And am running some unit tests on it:
[Fact]
public void CanBuild()
{
var content = new StringContent("some json", Enconding.UTF8, "application/json");
var dto = new ContentDto(content);
var contentHeaders = content.Headers.Flatten();
Assert.Equal(contentHeaders, dto.Headers);
}
And that test fails since the Content-Length header is not being captured on my dto. However if I do:
[Fact]
public void CanBuild()
{
var content = new StringContent("some json", Enconding.UTF8, "application/json");
var contentHeaders = content.Headers.Flatten();
var dto = new ContentDto(content);
Assert.Equal(contentHeaders, dto.Headers);
}
The test passes and all headers are captured. Even more I also tried this:
[Fact]
public void CanBuild()
{
var content = new StringContent("some json", Enconding.UTF8, "application/json");
var dto = new ContentDto(content);
var contentHeaders = content.Headers.Flatten();
var dto1 = new ContentDto(content);
Assert.Equal(contentHeaders, dto.Headers);
Assert.Equal(contentHeaders, dto1.Headers);
}
and it fails since dto doesn't have the Content-Length header, but dto1 does. I even tried getting the headers inside a Factory-like method like this:
public static ContentDto FromContent<T>(T content) where T : HttpContent
{
// same as the constructor
}
to see if there was something special about the StringContent class regarding the Content-Length headers, but it made no difference, no matter if I used the constructor (which uses the base class HttpContent) or the generic method FromContent (using the actual StringContent in this case) the result was the same.
So my questions are:
Is that the intended behavior of HttpContent.Headers?
Are there some headers specific to the actual HttpContent type?
What am I missing here?
Note: This is the code for the Flatten extension method:
public static string Flatten(this HttpHeaders headers)
{
var data = headers.ToDictionary(h => h.Key, h => string.Join("; ", h.Value))
.Select(kvp => $"{kvp.Key}: {kvp.Value}");
return string.Join(Environment.NewLine, data)
}
Your example is incomplete. I was only able to recreate your issue when I accessed the ContentLength property before calling the extension method. Somewhere in your code (most probably //rest of setup) you are either directly or indirectly calling that property which is most probably following a lazy loading pattern and it is then included in the header when next you call your extension method and it is included in the constructed string. They don't match because you are generating your manual string before accessing the content length property.
In the source code for HttpContentHeaders.ContentLength
public long? ContentLength
{
get
{
// 'Content-Length' can only hold one value. So either we get 'null' back or a boxed long value.
object storedValue = GetParsedValues(HttpKnownHeaderNames.ContentLength);
// Only try to calculate the length if the user didn't set the value explicitly using the setter.
if (!_contentLengthSet && (storedValue == null))
{
// If we don't have a value for Content-Length in the store, try to let the content calculate
// it's length. If the content object is able to calculate the length, we'll store it in the
// store.
long? calculatedLength = _calculateLengthFunc();
if (calculatedLength != null)
{
SetParsedValue(HttpKnownHeaderNames.ContentLength, (object)calculatedLength.Value);
}
return calculatedLength;
}
if (storedValue == null)
{
return null;
}
else
{
return (long)storedValue;
}
}
set
{
SetOrRemoveParsedValue(HttpKnownHeaderNames.ContentLength, value); // box long value
_contentLengthSet = true;
}
}
you can see that if you did not explicitly set a content length then it will add it (lazy load) to the headers when you first try to access it.
This proves my original theory about it being added after you generated/flatten your string and then accessed the ContentLength property and explains the inconsistent enumeration.
It seems that the HttpContent class has a pretty strange behavior with the headers properties. Somehow the content length seems to be computed as it is stated here. It does not address your issue specifically, but you can make a test with a new httpContent object similar to the initial one. I am pretty sure that you`ll be able to get the content length without a problem.